diff --git a/scapy/automaton.py b/scapy/automaton.py index 67d804d958f..320eac95bd1 100644 --- a/scapy/automaton.py +++ b/scapy/automaton.py @@ -855,6 +855,9 @@ def _do_iter(self): if (len(self.recv_conditions[self.state.state]) == 0 and len(self.ioevents[self.state.state]) == 0 and len(self.timeout[self.state.state]) == 1): + print("len(self.recv_conditions[self.state.state] : %d" % len(self.recv_conditions[self.state.state])) + print("len(self.ioevents[self.state.state]) : %d" % len(self.ioevents[self.state.state])) + print("len(self.timeout[self.state.state]) : %d" % len(self.timeout[self.state.state])) raise self.Stuck("stuck in [%s]" % self.state.state, state=self.state.state, result=state_output) # noqa: E501 diff --git a/scapy/layers/tls/automaton.py b/scapy/layers/tls/automaton.py index 7d8f0fd4573..45fc26a760e 100644 --- a/scapy/layers/tls/automaton.py +++ b/scapy/layers/tls/automaton.py @@ -138,20 +138,29 @@ def get_next_msg(self, socket_timeout=2, retry=2): # Remote peer is not willing to respond return - p = TLS(self.remain_in, tls_session=self.cur_session) - self.cur_session = p.tls_session - self.remain_in = b"" - if isinstance(p, SSLv2) and not p.msg: - p.msg = Raw("") - if self.cur_session.tls_version is None or \ - self.cur_session.tls_version < 0x0304: - self.buffer_in += p.msg + if (byte0 == 0x17 and + (self.cur_session.advertised_tls_version >= 0x0304 or + self.cur_session.tls_version >= 0x0304)): + p = TLS13(self.remain_in, tls_session=self.cur_session) + self.remain_in = b"" + self.buffer_in += p.inner.msg else: - if isinstance(p, TLS13): - self.buffer_in += p.inner.msg - else: - # should be TLS13ServerHello only + + p = TLS(self.remain_in, tls_session=self.cur_session) + self.cur_session = p.tls_session + self.remain_in = b"" + if isinstance(p, SSLv2) and not p.msg: + p.msg = Raw("") + + if self.cur_session.tls_version is None or \ + self.cur_session.tls_version < 0x0304: self.buffer_in += p.msg + else: + if isinstance(p, TLS13): + self.buffer_in += p.inner.msg + else: + # should be TLS13ServerHello only + self.buffer_in += p.msg while p.payload: if isinstance(p.payload, Raw): @@ -174,8 +183,11 @@ def raise_on_packet(self, pkt_cls, state, get_next_msg=True): # Maybe we already parsed the expected packet, maybe not. if get_next_msg: self.get_next_msg() + from scapy.layers.tls.handshake import TLSClientHello if (not self.buffer_in or - not isinstance(self.buffer_in[0], pkt_cls)): + (not isinstance(self.buffer_in[0], pkt_cls) and + not (isinstance(self.buffer_in[0], TLSClientHello) and + self.cur_session.advertised_tls_version == 0x0304))): return self.cur_pkt = self.buffer_in[0] self.buffer_in = self.buffer_in[1:] @@ -199,6 +211,7 @@ def add_record(self, is_sslv2=None, is_tls13=None): else: self.buffer_out.append(TLS(tls_session=self.cur_session)) + def add_msg(self, pkt): """ Add a TLS message (e.g. TLSClientHello or TLSApplicationData) @@ -217,6 +230,8 @@ def flush_records(self): """ Send all buffered records and update the session accordingly. """ + for p in self.buffer_out: + p.show() s = b"".join(p.raw_stateful() for p in self.buffer_out) self.socket.send(s) self.buffer_out = [] diff --git a/scapy/layers/tls/automaton_cli.py b/scapy/layers/tls/automaton_cli.py index 765ee5e3bd9..a3ad58c1e7a 100644 --- a/scapy/layers/tls/automaton_cli.py +++ b/scapy/layers/tls/automaton_cli.py @@ -19,30 +19,46 @@ from __future__ import print_function import socket +import binascii +import struct +import time from scapy.pton_ntop import inet_pton from scapy.utils import randstring, repr_hex from scapy.automaton import ATMT +from scapy.modules import six +from scapy.packet import Raw +from scapy.compat import bytes_encode + from scapy.layers.tls.automaton import _TLSAutomaton from scapy.layers.tls.basefields import _tls_version, _tls_version_options from scapy.layers.tls.session import tlsSession from scapy.layers.tls.extensions import TLS_Ext_SupportedGroups, \ - TLS_Ext_SupportedVersions, TLS_Ext_SignatureAlgorithms + TLS_Ext_SupportedVersion_CH, TLS_Ext_SignatureAlgorithms, \ + TLS_Ext_PostHandshakeAuth, TLS_Ext_PSKKeyExchangeModes, \ + TLS_Ext_EarlyDataIndication, TLS_Ext_SupportedVersion_SH from scapy.layers.tls.handshake import TLSCertificate, TLSCertificateRequest, \ - TLSCertificateVerify, TLSClientHello, TLSClientKeyExchange, \ - TLSEncryptedExtensions, TLSFinished, TLSServerHello, TLSServerHelloDone, \ - TLSServerKeyExchange, TLS13Certificate, TLS13ServerHello + TLSCertificateVerify, TLSClientHello, TLS13ClientHello, _ASN1CertAndExt, \ + TLSClientKeyExchange, TLSEncryptedExtensions, TLSFinished, \ + TLSServerHelloDone, TLSServerKeyExchange, TLS13Certificate, \ + TLS13ServerHello, TLS13KeyUpdate, TLS13CertificateRequest, \ + TLS13NewSessionTicket, TLS13EndOfEarlyData, TLS13HelloRetryRequest, \ + TLSServerHello from scapy.layers.tls.handshake_sslv2 import SSLv2ClientHello, \ SSLv2ServerHello, SSLv2ClientMasterKey, SSLv2ServerVerify, \ SSLv2ClientFinished, SSLv2ServerFinished, SSLv2ClientCertificate, \ SSLv2RequestCertificate from scapy.layers.tls.keyexchange_tls13 import TLS_Ext_KeyShare_CH, \ - KeyShareEntry + KeyShareEntry, TLS_Ext_PreSharedKey_CH, PSKIdentity, PSKBinderEntry, \ + TLS_Ext_KeyShare_HRR + from scapy.layers.tls.record import TLSAlert, TLSChangeCipherSpec, \ TLSApplicationData -from scapy.modules import six -from scapy.packet import Raw -from scapy.compat import bytes_encode + +from scapy.layers.tls.crypto.hkdf import TLS13_HKDF +from scapy.layers.tls.crypto.suites import _tls_cipher_suites, \ + _tls_cipher_suites_cls +from scapy.layers.tls.crypto.groups import _tls_named_groups class TLSClientAutomaton(_TLSAutomaton): @@ -67,7 +83,14 @@ class TLSClientAutomaton(_TLSAutomaton): def parse_args(self, server="127.0.0.1", dport=4433, server_name=None, mycert=None, mykey=None, client_hello=None, version=None, - data=None, + data=None, psk=None, psk_mode=None, + resumption_master_secret=None, + session_ticket_file_in=None, + session_ticket_file_out=None, + early_data_file=None, + ciphersuite=None, + curve=None, + post_handshake_auth=False, **kargs): super(TLSClientAutomaton, self).parse_args(mycert=mycert, @@ -113,6 +136,33 @@ def parse_args(self, server="127.0.0.1", dport=4433, server_name=None, else: self.data_to_send = [] + self.ciphersuite = None + if ciphersuite is not None: + cs = int(ciphersuite, 16) + if cs in _tls_cipher_suites.keys(): + self.ciphersuite = cs + + if self.advertised_tls_version == 0x0304: + self.tls13_psk_secret = psk + self.tls13_psk_mode = psk_mode + + # XXX args for tlsSession not TLSAutomaton... + self.resumption_master_secret = resumption_master_secret + self.session_ticket_file_in = session_ticket_file_in + self.session_ticket_file_out = session_ticket_file_out + + if early_data_file: + with open(early_data_file, "r") as f: + self.early_data = f.read() + else: + self.early_data = None + + self.curve = None + for (group_id, ng) in _tls_named_groups.items(): + if ng == curve: + self.curve = group_id + self.post_handshake_auth = post_handshake_auth + def vprint_sessioninfo(self): if self.verbose: s = self.cur_session @@ -127,6 +177,11 @@ def vprint_sessioninfo(self): self.vprint("Master secret : %s" % repr_hex(ms)) if s.server_certs: self.vprint("Server certificate chain: %r" % s.server_certs) + + if s.tls_version >= 0x0304: + res_secret = s.tls13_derived_secrets["resumption_secret"] + self.vprint("resumption master secret : %s" % + repr_hex(res_secret)) self.vprint() @ATMT.state(initial=True) @@ -137,14 +192,59 @@ def INITIAL(self): @ATMT.state() def INIT_TLS_SESSION(self): self.cur_session = tlsSession(connection_end="client") - self.cur_session.client_certs = self.mycert - self.cur_session.client_key = self.mykey + s = self.cur_session + s.client_certs = self.mycert + s.client_key = self.mykey v = self.advertised_tls_version if v: - self.cur_session.advertised_tls_version = v + s.advertised_tls_version = v else: - default_version = self.cur_session.advertised_tls_version + default_version = s.advertised_tls_version self.advertised_tls_version = default_version + + + if s.advertised_tls_version >= 0x0304: + if self.tls13_psk_secret: + s.tls13_psk_secret = binascii.unhexlify(self.tls13_psk_secret) + + if self.session_ticket_file_in: + with open(self.session_ticket_file_in, 'rb') as f: + + resumed_ciphersuite_len = struct.unpack("B", f.read(1))[0] + s.tls13_ticket_ciphersuite = struct.unpack("!H", f.read(resumed_ciphersuite_len))[0] + + ticket_nonce_len = struct.unpack("B", f.read(1))[0] + # XXX add client_session_nonce member in tlsSession + s.client_session_nonce = f.read(ticket_nonce_len) + + client_ticket_age_len = struct.unpack("!H", f.read(2))[0] + tmp = f.read(client_ticket_age_len) + s.client_ticket_age = struct.unpack("!I", tmp)[0] + + client_ticket_age_add_len = struct.unpack("!H", f.read(2))[0] + tmp = f.read(client_ticket_age_add_len) + s.client_session_ticket_age_add = struct.unpack("!I", tmp)[0] + + ticket_len = struct.unpack("!H", f.read(2))[0] + s.client_session_ticket = f.read(ticket_len) + + if self.resumption_master_secret: + + if s.tls13_ticket_ciphersuite not in _tls_cipher_suites_cls: + warning("Unknown cipher suite %d" % s.tls13_ticket_ciphersuite) # noqa: E501 + # we do not try to set a default nor stop the execution + else: + cs_cls = _tls_cipher_suites_cls[s.tls13_ticket_ciphersuite] + + hkdf = TLS13_HKDF(cs_cls.hash_alg.name.lower()) + hash_len = hkdf.hash.digest_size + + s.tls13_psk_secret = hkdf.expand_label(binascii.unhexlify(self.resumption_master_secret), # noqa: E501 + b"resumption", + s.client_session_nonce, + hash_len) + + self.vprint("[debug] resumption PSK : %s" % s.tls13_psk_secret) raise self.CONNECT() @ATMT.state() @@ -358,7 +458,7 @@ def should_add_ClientVerify(self): """ hs_msg = [type(m) for m in self.cur_session.handshake_messages_parsed] if (TLSCertificateRequest not in hs_msg or - self.mycert is None or + self.mycert is None or self.mykey is None): return self.add_msg(TLSCertificateVerify()) @@ -455,11 +555,19 @@ def add_ClientData(self): data = self.data_to_send.pop() if data == b"quit": return - if self.linebreak: - data += b"\n" - self.add_record() - self.add_msg(TLSApplicationData(data=data)) - raise self.ADDED_CLIENTDATA() + + # Special OpenSSL commands to perform certain operations + # See openssl s_server man + elif data == b"k": + self.add_record() + self.add_msg(TLS13KeyUpdate(request_update="update_requested")) + raise self.ADDED_CLIENTDATA() + else: + if self.linebreak: + data += b"\n" + self.add_record() + self.add_msg(TLSApplicationData(data=data)) + raise self.ADDED_CLIENTDATA() @ATMT.condition(WAIT_CLIENTDATA, prio=2) def no_more_ClientData(self): @@ -491,12 +599,63 @@ def RECEIVED_SERVERDATA(self): def should_handle_ServerData(self): if not self.buffer_in: raise self.WAIT_CLIENTDATA() + p = self.buffer_in[0] if isinstance(p, TLSApplicationData): print("> Received: %r" % p.data) + elif isinstance(p, TLS13CertificateRequest): + self.add_record() + certs = [] + if self.mycert: + certs += _ASN1CertAndExt(cert=self.mycert) + + self.add_msg(TLS13Certificate(certs=certs)) + if self.mycert and self.mykey: + self.add_msg(TLSCertificateVerify()) + self.add_msg(TLSFinished()) + raise self.ADDED_CLIENTDATA() + elif isinstance(p, TLSAlert): print("> Received: %r" % p) raise self.CLOSE_NOTIFY() + elif isinstance(p, TLS13NewSessionTicket): + print("> Received: %r [2]" % p) + + # If arg session_ticket_file_out is set, we save + # Save the ticket for resumption... + if self.session_ticket_file_out: + # Struct of ticket file : + # * ciphersuite_len (1 byte) + # * ciphersuite (ciphersuite_len bytes) : + # we need to the store the ciphersuite for resumption + # * ticket_nonce_len (1 byte) + # * ticket_nonce (ticket_nonce_len bytes) : + # we need to store the nonce to compute the PSK + # for resumption + # * ticket_age_len (2 bytes) + # * ticket_age (ticket_age_len bytes) : + # we need to store the time we received the ticket for + # computing the obfuscated_ticket_age when resuming + # * ticket_age_add_len (2 bytes) + # * ticket_age_add (ticket_age_add_len bytes) : + # we need to store the ticket_age_add value from the + # ticket to compute the obfuscated ticket age + # * ticket_len (2 bytes) + # * ticket (ticket_len bytes) + with open(self.session_ticket_file_out, 'wb') as f: + f.write(struct.pack("B", 2)) + # we choose wcs arbritary... + f.write(struct.pack("!H", + self.cur_session.wcs.ciphersuite.val)) + f.write(struct.pack("B", p.noncelen)) + f.write(p.ticket_nonce) + f.write(struct.pack("!H", 4)) + f.write(struct.pack("!I", int(time.time()))) + f.write(struct.pack("!H", 4)) + f.write(struct.pack("!I", p.ticket_age_add)) + f.write(struct.pack("!H", p.ticketlen)) + f.write(self.cur_session.client_session_ticket) + else: print("> Received: %r" % p) self.buffer_in = self.buffer_in[1:] @@ -820,6 +979,8 @@ def TLS13_START(self): @ATMT.condition(TLS13_START) def tls13_should_add_ClientHello(self): # we have to use the legacy, plaintext TLS record here + + supported_groups = ["secp256r1", "secp384r1", "x25519"] self.add_record(is_tls13=False) if self.client_hello: p = self.client_hello @@ -827,98 +988,369 @@ def tls13_should_add_ClientHello(self): # When trying to connect to a public TLS 1.3 server, # you will most likely need to provide an SNI extension. # sn = ServerName(servername="") - ext = [TLS_Ext_SupportedGroups(groups=["secp256r1"]), - # TLS_Ext_ServerName(servernames=[sn]), - TLS_Ext_KeyShare_CH(client_shares=[KeyShareEntry(group=23)]), # noqa: E501 - TLS_Ext_SupportedVersions(versions=["TLS 1.3-d18"]), - TLS_Ext_SignatureAlgorithms(sig_algs=["sha256+rsapss", - "sha256+rsa"])] - p = TLSClientHello(ciphers=0x1301, ext=ext) + + ext = [] + ext += TLS_Ext_SupportedVersion_CH(versions=["TLS 1.3"]), + + # The "post_handshake_auth" extension is used to indicate that a + # client is willing to perform post-handshake authentication + if self.cur_session.post_handshake_auth: + ext += TLS_Ext_PostHandshakeAuth() + + if self.cur_session.tls13_psk_secret: + + if self.early_data: + ext += TLS_Ext_EarlyDataIndication() + + if self.tls13_psk_mode == "psk_dhe_ke": + ext += [TLS_Ext_PSKKeyExchangeModes(kxmodes="psk_dhe_ke"), + TLS_Ext_SupportedGroups(groups=supported_groups), # noqa: E501 + TLS_Ext_KeyShare_CH(client_shares=[KeyShareEntry(group=self.curve)])] # noqa: E501 + else: + ext += TLS_Ext_PSKKeyExchangeModes(kxmodes="psk_ke") + # RFC844, section 4.2.11. + # "The "pre_shared_key" extension MUST be the last extension + # in the ClientHello " + + s = self.cur_session + if s.client_session_ticket: + cs_cls = _tls_cipher_suites_cls[s.tls13_ticket_ciphersuite] # noqa: E501 + hkdf = TLS13_HKDF(cs_cls.hash_alg.name.lower()) + hash_len = hkdf.hash.digest_size + + # We compute the client's view of the age of the ticket (ie + # the time since the receipt of the ticket) in ms + agems = int((time.time() - s.client_ticket_age) * 1000) + + # Then we compute the obfuscated version of the ticket age + # by adding the "ticket_age_add" value included in the + # ticket (modulo 2^32) + obfuscated_age = ((agems + s.client_session_ticket_age_add) + & 0xffffffff) + + psk_id = PSKIdentity(identity=s.client_session_ticket, + obfuscated_ticket_age=obfuscated_age) + + psk_binder_entry = PSKBinderEntry(binder_len=hash_len, + binder=b"\x00"*hash_len) + ext += TLS_Ext_PreSharedKey_CH(identities=[psk_id], + binders=[psk_binder_entry]) + else: + hkdf = TLS13_HKDF("sha256") + hash_len = hkdf.hash.digest_size + psk_id = PSKIdentity(identity='Client_identity') + psk_binder_entry = PSKBinderEntry(binder_len=hash_len, + binder=b"\x00"*hash_len) + + ext += TLS_Ext_PreSharedKey_CH(identities=[psk_id], + binders=[psk_binder_entry]) + else: + ext += [TLS_Ext_SupportedGroups(groups=supported_groups), + # TLS_Ext_ServerName(servernames=[sn]), + TLS_Ext_KeyShare_CH(client_shares=[KeyShareEntry(group=self.curve)]), # noqa: E501 + TLS_Ext_SignatureAlgorithms(sig_algs=["sha256+rsaepss"]) # noqa: E501 + ] + if self.ciphersuite is None: + c = 0x1301 + else: + c = self.ciphersuite + p = TLS13ClientHello(ciphers=c, ext=ext) self.add_msg(p) + raise self.TLS13_ADDED_CLIENTHELLO() @ATMT.state() def TLS13_ADDED_CLIENTHELLO(self): + if self.early_data: + self.add_record(is_tls13=True) + self.add_msg(TLSApplicationData(data=self.early_data)) + raise self.TLS13_SENDING_CLIENTFLIGHT1() + + @ATMT.state() + def TLS13_SENDING_CLIENTFLIGHT1(self): pass - @ATMT.condition(TLS13_ADDED_CLIENTHELLO) - def tls13_should_send_ClientHello(self): + @ATMT.condition(TLS13_SENDING_CLIENTFLIGHT1) + def tls13_should_send_ClientFlight1(self): self.flush_records() - raise self.TLS13_SENT_CLIENTHELLO() + raise self.TLS13_SENT_CLIENTFLIGHT1() @ATMT.state() - def TLS13_SENT_CLIENTHELLO(self): - raise self.TLS13_WAITING_SERVERHELLO() + def TLS13_SENT_CLIENTFLIGHT1(self): + raise self.TLS13_WAITING_SERVERFLIGHT1() @ATMT.state() - def TLS13_WAITING_SERVERHELLO(self): + def TLS13_WAITING_SERVERFLIGHT1(self): self.get_next_msg() + raise self.TLS13_RECEIVED_SERVERFLIGHT1() + + @ATMT.state() + def TLS13_RECEIVED_SERVERFLIGHT1(self): + pass - @ATMT.condition(TLS13_WAITING_SERVERHELLO) + @ATMT.condition(TLS13_RECEIVED_SERVERFLIGHT1, prio=1) def tls13_should_handle_ServerHello(self): + """ + XXX We should check the ServerHello attributes for discrepancies with + our own ClientHello. + """ self.raise_on_packet(TLS13ServerHello, - self.TLS13_WAITING_ENCRYPTEDEXTENSIONS) + self.TLS13_HANDLED_SERVERHELLO) + + @ATMT.condition(TLS13_RECEIVED_SERVERFLIGHT1, prio=2) + def tls13_should_handle_HelloRetryRequest(self): + """ + XXX We should check the ServerHello attributes for discrepancies with + our own ClientHello. + """ + self.raise_on_packet(TLS13HelloRetryRequest, + self.TLS13_HELLO_RETRY_REQUESTED) + + @ATMT.condition(TLS13_RECEIVED_SERVERFLIGHT1, prio=3) + def tls13_should_handle_AlertMessage_(self): + self.raise_on_packet(TLSAlert, + self.CLOSE_NOTIFY) + + @ATMT.condition(TLS13_RECEIVED_SERVERFLIGHT1, prio=4) + def tls13_missing_ServerHello(self): + raise self.MISSING_SERVERHELLO() @ATMT.state() - def TLS13_WAITING_ENCRYPTEDEXTENSIONS(self): - self.get_next_msg() + def TLS13_HELLO_RETRY_REQUESTED(self): + pass + + @ATMT.condition(TLS13_HELLO_RETRY_REQUESTED) + def tls13_should_add_ClientHello_Retry(self): + s = self.cur_session + s.tls13_retry = True + + # we have to use the legacy, plaintext TLS record here + self.add_record(is_tls13=False) + + # We retrieve the group to be used and the selected version from the + # previous message + hrr = s.handshake_messages_parsed[-1] + + if not isinstance(hrr, TLS13HelloRetryRequest): + pass + + if hrr.ext: + for e in hrr.ext: + if isinstance(e, TLS_Ext_KeyShare_HRR): + selected_group = e.selected_group + + if isinstance(e, TLS_Ext_SupportedVersion_SH): + selected_version = e.version + + if not selected_group or not selected_version: + raise self.CLOSE_NOTIFY() + + ext = [] + ext += TLS_Ext_SupportedVersion_CH(versions=[_tls_version[selected_version]]) # noqa: E501 + + # The "post_handshake_auth" extension is used to indicate that a client + # is willing to perform post-handshake authentication + if s.post_handshake_auth: + ext += TLS_Ext_PostHandshakeAuth() + + if s.tls13_psk_secret: + + if self.tls13_psk_mode == "psk_dhe_ke": + ext += [TLS_Ext_PSKKeyExchangeModes(kxmodes="psk_dhe_ke"), + TLS_Ext_SupportedGroups(groups=[_tls_named_groups[selected_group]]), # noqa: E501 + TLS_Ext_KeyShare_CH(client_shares=[KeyShareEntry(group=selected_group)]) # noqa: E501 + ] + else: + ext += [TLS_Ext_PSKKeyExchangeModes(kxmodes="psk_ke")] + + if s.client_session_ticket: + + # XXX Retrieve parameters from first ClientHello... + cs_cls = _tls_cipher_suites_cls[s.tls13_ticket_ciphersuite] + hkdf = TLS13_HKDF(cs_cls.hash_alg.name.lower()) + hash_len = hkdf.hash.digest_size + + # We compute the client's view of the age of the ticket (ie + # the time since the receipt of the ticket) in ms + agems = int((time.time() - s.client_ticket_age) * 1000) + + # Then we compute the obfuscated version of the ticket age by + # adding the "ticket_age_add" value included in the ticket + # (modulo 2^32) + obfuscated_age = ((agems + s.client_session_ticket_age_add) + & 0xffffffff) + + psk_id = PSKIdentity(identity=s.client_session_ticket, + obfuscated_ticket_age=obfuscated_age) - @ATMT.condition(TLS13_WAITING_ENCRYPTEDEXTENSIONS) - def tls13_should_handle_EncryptedExtensions(self): + psk_binder_entry = PSKBinderEntry(binder_len=hash_len, + binder=b"\x00"*hash_len) + + ext += TLS_Ext_PreSharedKey_CH(identities=[psk_id], + binders=[psk_binder_entry]) + + else: + hkdf = TLS13_HKDF("sha256") + hash_len = hkdf.hash.digest_size + psk_id = PSKIdentity(identity='Client_identity') + psk_binder_entry = PSKBinderEntry(binder_len=hash_len, + binder=b"\x00"*hash_len) + + ext += TLS_Ext_PreSharedKey_CH(identities=[psk_id], + binders=[psk_binder_entry]) + + else: + ext += [TLS_Ext_SupportedGroups(groups=[_tls_named_groups[selected_group]]), # noqa: E501 + TLS_Ext_KeyShare_CH(client_shares=[KeyShareEntry(group=selected_group)]), # noqa: E501 + TLS_Ext_SignatureAlgorithms(sig_algs=["sha256+rsaepss"]) + ] + + p = TLS13ClientHello(ciphers=0x1301, ext=ext) + + self.add_msg(p) + + raise self.TLS13_ADDED_CLIENTHELLO() + + @ATMT.state() + def TLS13_HANDLED_SERVERHELLO(self): + pass + + @ATMT.condition(TLS13_HANDLED_SERVERHELLO, prio=1) + def tls13_should_handle_encrytpedExtensions(self): self.raise_on_packet(TLSEncryptedExtensions, - self.TLS13_WAITING_CERTIFICATE) + self.TLS13_HANDLED_ENCRYPTEDEXTENSIONS) + + @ATMT.condition(TLS13_HANDLED_SERVERHELLO, prio=1) + def tls13_missing_encryptedExtension(self): + self.vprint("Missing TLS 1.3 EncryptedExtensions message!") + raise self.CLOSE_NOTIFY() @ATMT.state() - def TLS13_WAITING_CERTIFICATE(self): - self.get_next_msg() + def TLS13_HANDLED_ENCRYPTEDEXTENSIONS(self): + pass - @ATMT.condition(TLS13_WAITING_CERTIFICATE, prio=1) - def tls13_should_handle_Certificate(self): - self.raise_on_packet(TLS13Certificate, - self.TLS13_WAITING_CERTIFICATEVERIFY) + @ATMT.condition(TLS13_HANDLED_ENCRYPTEDEXTENSIONS, prio=1) + def tls13_should_handle_certificateRequest_from_encryptedExtensions(self): + """ + XXX We should check the CertificateRequest attributes for discrepancies + with the cipher suite, etc. + """ + self.raise_on_packet(TLS13CertificateRequest, + self.TLS13_HANDLED_CERTIFICATEREQUEST) - @ATMT.condition(TLS13_WAITING_CERTIFICATE, prio=2) - def tls13_should_handle_CertificateRequest(self): - hs_msg = [type(m) for m in self.cur_session.handshake_messages_parsed] - if TLSCertificateRequest in hs_msg: - self.vprint("TLSCertificateRequest already received!") - self.raise_on_packet(TLSCertificateRequest, - self.TLS13_WAITING_CERTIFICATE) + @ATMT.condition(TLS13_HANDLED_ENCRYPTEDEXTENSIONS, prio=2) + def tls13_should_handle_certificate_from_encryptedExtensions(self): + self.tls13_should_handle_Certificate() - @ATMT.condition(TLS13_WAITING_CERTIFICATE, prio=3) - def tls13_should_handle_ServerFinished_from_EncryptedExtensions(self): - self.raise_on_packet(TLSFinished, - self.TLS13_CONNECTED) + @ATMT.condition(TLS13_HANDLED_ENCRYPTEDEXTENSIONS, prio=3) + def tls13_should_handle_finished_from_encryptedExtensions(self): + if self.cur_session.tls13_psk_secret: + self.raise_on_packet(TLSFinished, + self.TLS13_HANDLED_FINISHED) - @ATMT.condition(TLS13_WAITING_CERTIFICATE, prio=4) - def tls13_missing_Certificate(self): - self.vprint("Missing TLS 1.3 message after EncryptedExtensions!") - raise self.FINAL() + @ATMT.state() + def TLS13_HANDLED_CERTIFICATEREQUEST(self): + pass + + @ATMT.condition(TLS13_HANDLED_CERTIFICATEREQUEST, prio=1) + def tls13_should_handle_ServerHelloDone_from_CertificateRequest(self): + return self.tls13_should_handle_Certificate() + + def tls13_should_handle_Certificate(self): + self.raise_on_packet(TLS13Certificate, + self.TLS13_HANDLED_CERTIFICATE) @ATMT.state() - def TLS13_WAITING_CERTIFICATEVERIFY(self): - self.get_next_msg() + def TLS13_HANDLED_CERTIFICATE(self): + pass - @ATMT.condition(TLS13_WAITING_CERTIFICATEVERIFY) + @ATMT.condition(TLS13_HANDLED_CERTIFICATE, prio=1) def tls13_should_handle_CertificateVerify(self): + self.raise_on_packet(TLSCertificateVerify, - self.TLS13_WAITING_SERVERFINISHED) + self.TLS13_HANDLED_CERTIFICATE_VERIFY) + + @ATMT.condition(TLS13_HANDLED_CERTIFICATE, prio=2) + def tls13_missing_CertificateVerify(self): + self.vprint("Missing TLS 1.3 CertificateVerify message!") + raise self.CLOSE_NOTIFY() @ATMT.state() - def TLS13_WAITING_SERVERFINISHED(self): - self.get_next_msg() + def TLS13_HANDLED_CERTIFICATE_VERIFY(self): + pass - @ATMT.condition(TLS13_WAITING_SERVERFINISHED) - def tls13_should_handle_ServerFinished_from_CertificateVerify(self): + @ATMT.condition(TLS13_HANDLED_CERTIFICATE_VERIFY, prio=1) + def tls13_should_handle_finished(self): self.raise_on_packet(TLSFinished, - self.TLS13_PREPARE_CLIENTFLIGHT2) + self.TLS13_HANDLED_FINISHED) + + @ATMT.state() + def TLS13_HANDLED_FINISHED(self): + raise self.TLS13_PREPARE_CLIENTFLIGHT2() @ATMT.state() def TLS13_PREPARE_CLIENTFLIGHT2(self): self.add_record(is_tls13=True) # raise self.FINAL() + if self.early_data: + self.add_msg(TLS13EndOfEarlyData()) + self.add_record(is_tls13=True) + + @ATMT.condition(TLS13_PREPARE_CLIENTFLIGHT2, prio=1) + def tls13_should_add_ClientCertificate(self): + """ + If the server sent a CertificateRequest, we send a Certificate message. + If no certificate is available, an empty Certificate message is sent: + - this is a SHOULD in RFC 4346 (Section 7.4.6) + - this is a MUST in RFC 5246 (Section 7.4.6) + + XXX We may want to add a complete chain. + """ + hs_msg = [type(m) for m in self.cur_session.handshake_messages_parsed] + if TLS13CertificateRequest not in hs_msg: + raise self.TLS13_ADDED_CLIENTCERTIFICATE() + # return + certs = [] + if self.mycert: + certs += _ASN1CertAndExt(cert=self.mycert) + + self.add_msg(TLS13Certificate(certs=certs)) + raise self.TLS13_ADDED_CLIENTCERTIFICATE() + + @ATMT.state() + def TLS13_ADDED_CLIENTCERTIFICATE(self): + pass + + # @ATMT.condition(TLS13_ADDED_CLIENTCERTIFICATE, prio=2) + # def tls13_should_add_ClientFinished(self): + # self.vprint("tls13_should_add_ClientFinished") + # return self.tls13_should_add_ClientFinished() + + @ATMT.condition(TLS13_ADDED_CLIENTCERTIFICATE, prio=1) + def tls13_should_add_ClientCertificateVerify(self): + """ + XXX Section 7.4.7.1 of RFC 5246 states that the CertificateVerify + message is only sent following a client certificate that has signing + capability (i.e. not those containing fixed DH params). + We should verify that before adding the message. We should also handle + the case when the Certificate message was empty. + """ + hs_msg = [type(m) for m in self.cur_session.handshake_messages_parsed] + if (TLS13CertificateRequest not in hs_msg or + self.mycert is None or + self.mykey is None): + return self.tls13_should_add_ClientFinished() + self.add_msg(TLSCertificateVerify()) + raise self.TLS13_ADDED_CERTIFICATEVERIFY() + + @ATMT.state() + def TLS13_ADDED_CERTIFICATEVERIFY(self): + return self.tls13_should_add_ClientFinished() + + @ATMT.state() + def TLS13_ADDED_CLIENTFINISHED(self): + pass - @ATMT.condition(TLS13_PREPARE_CLIENTFLIGHT2) def tls13_should_add_ClientFinished(self): self.add_msg(TLSFinished()) raise self.TLS13_ADDED_CLIENTFINISHED() @@ -934,7 +1366,10 @@ def tls13_should_send_ClientFlight2(self): @ATMT.state() def TLS13_SENT_CLIENTFLIGHT2(self): - raise self.HANDLED_SERVERFINISHED() + self.vprint("TLS 1.3 handshake completed!") + self.vprint_sessioninfo() + self.vprint("You may send data or use 'quit'.") + raise self.WAIT_CLIENTDATA() @ATMT.state(final=True) def FINAL(self): diff --git a/scapy/layers/tls/automaton_srv.py b/scapy/layers/tls/automaton_srv.py index ffcacbd115a..e2ffe7d350c 100644 --- a/scapy/layers/tls/automaton_srv.py +++ b/scapy/layers/tls/automaton_srv.py @@ -12,12 +12,16 @@ In order to run a server listening on tcp/4433: > from scapy.all import * -> t = TLSServerAutomaton(mycert='', mykey='') +> t = TLSServerAutomaton(mycert +='', mykey='') > t.run() """ from __future__ import print_function import socket +import binascii +import struct +import time from scapy.packet import Raw from scapy.pton_ntop import inet_pton @@ -27,17 +31,36 @@ from scapy.layers.tls.cert import PrivKeyRSA, PrivKeyECDSA from scapy.layers.tls.basefields import _tls_version from scapy.layers.tls.session import tlsSession + +from scapy.layers.tls.crypto.hkdf import TLS13_HKDF +from scapy.layers.tls.crypto.groups import _tls_named_groups + +from scapy.layers.tls.extensions import TLS_Ext_SupportedVersion_SH, \ + TLS_Ext_SignatureAlgorithms, TLS_Ext_PostHandshakeAuth, \ + TLS_Ext_PSKKeyExchangeModes, TLS_Ext_EarlyDataIndicationTicket, \ + TLS_Ext_EarlyDataIndication, TLS_Ext_SupportedGroups, \ + TLS_Ext_Cookie +from scapy.layers.tls.keyexchange_tls13 import TLS_Ext_KeyShare_SH, \ + KeyShareEntry, TLS_Ext_PreSharedKey_SH, TLS_Ext_PreSharedKey_CH, \ + TLS_Ext_KeyShare_HRR + from scapy.layers.tls.handshake import TLSCertificate, TLSCertificateRequest, \ TLSCertificateVerify, TLSClientHello, TLSClientKeyExchange, TLSFinished, \ - TLSServerHello, TLSServerHelloDone, TLSServerKeyExchange + TLSServerHello, TLSServerHelloDone, TLSServerKeyExchange, _ASN1CertAndExt, \ + TLS13ClientHello, TLS13ServerHello, TLSEncryptedExtensions, \ + TLS13KeyUpdate, TLS13Certificate, TLS13HelloRetryRequest, \ + TLS13CertificateRequest, TLS13NewSessionTicket, TLS13EndOfEarlyData + from scapy.layers.tls.handshake_sslv2 import SSLv2ClientCertificate, \ SSLv2ClientFinished, SSLv2ClientHello, SSLv2ClientMasterKey, \ SSLv2RequestCertificate, SSLv2ServerFinished, SSLv2ServerHello, \ SSLv2ServerVerify from scapy.layers.tls.record import TLSAlert, TLSChangeCipherSpec, \ TLSApplicationData +from scapy.layers.tls.record_tls13 import TLS13 from scapy.layers.tls.crypto.suites import _tls_cipher_suites_cls, \ get_usable_ciphersuites +from scapy.layers.tls.session import writeConnState class TLSServerAutomaton(_TLSAutomaton): @@ -70,6 +93,12 @@ def parse_args(self, server="127.0.0.1", sport=4433, client_auth=False, is_echo_server=True, max_client_idle_time=60, + psk=None, + psk_mode=None, + early_data=False, + session_ticket_file=None, + curve=None, + cookie=False, **kargs): super(TLSServerAutomaton, self).parse_args(mycert=mycert, @@ -91,10 +120,38 @@ def parse_args(self, server="127.0.0.1", sport=4433, self.remote_ip = None self.remote_port = None - self.preferred_ciphersuite = preferred_ciphersuite + self.preferred_ciphersuite = None + if preferred_ciphersuite is not None: + pcs = int(preferred_ciphersuite, 16) + if pcs in _tls_cipher_suites.keys(): + self.preferred_ciphersuite = pcs + self.client_auth = client_auth self.is_echo_server = is_echo_server self.max_client_idle_time = max_client_idle_time + self.handle_session_ticket = (session_ticket_file is not None) + self.client_auth = client_auth + self.max_early_data_size = 0 + self.resumed_ciphersuite = None + + if psk: + self.psk_secret = psk + + if psk_mode: + self.psk_mode = psk_mode + + # early_data is a flag to indicate wether or not the server + # will try to read early_data after the missing_ClientHello + self.early_data = early_data + self.early_data_was_accepted = False + self.cookie = cookie + + self.session_ticket_file = session_ticket_file + + self.curve = None + for (group_id, ng) in _tls_named_groups.items(): + if ng == curve: + self.curve = group_id def vprint_sessioninfo(self): if self.verbose: @@ -103,10 +160,19 @@ def vprint_sessioninfo(self): self.vprint("Version : %s" % v) cs = s.wcs.ciphersuite.name self.vprint("Cipher suite : %s" % cs) - ms = s.master_secret + if s.tls_version < 0x0304: + ms = s.master_secret + else: + ms = s.tls13_master_secret self.vprint("Master secret : %s" % repr_hex(ms)) if s.client_certs: self.vprint("Client certificate chain: %r" % s.client_certs) + + if s.tls_version >= 0x0304: + res_secret = s.tls13_derived_secrets["resumption_secret"] + self.vprint("Resumption master secret : %s" % + repr_hex(res_secret)) + self.vprint() def http_sessioninfo(self): @@ -122,7 +188,11 @@ def http_sessioninfo(self): s += "Version : %s\n" % v cs = self.cur_session.wcs.ciphersuite.name s += "Cipher suite : %s\n" % cs - ms = self.cur_session.master_secret + if self.cur_session.tls_version < 0x0304: + ms = self.cur_session.master_secret + else: + ms = self.cur_session.tls13_master_secret + s += "Master secret : %s\n" % repr_hex(ms) body = "
%s
\r\n\r\n" % s answer = (header + body) % len(body) @@ -159,6 +229,8 @@ def BIND(self): @ATMT.state() def WAITING_CLIENT(self): self.vprint() + self.buffer_in = [] + self.buffer_out = [] self.vprint("Waiting for a new client on %s:%d" % (self.local_ip, self.local_port)) self.socket, addr = self.serversocket.accept() @@ -184,8 +256,7 @@ def INIT_TLS_SESSION(self): self.cur_session.server_key = self.mykey if isinstance(self.mykey, PrivKeyRSA): self.cur_session.server_rsa_key = self.mykey - # elif isinstance(self.mykey, PrivKeyECDSA): - # self.cur_session.server_ecdsa_key = self.mykey + raise self.WAITING_CLIENTFLIGHT1() @ATMT.state() @@ -200,6 +271,11 @@ def RECEIVED_CLIENTFLIGHT1(self): # TLS handshake # @ATMT.condition(RECEIVED_CLIENTFLIGHT1, prio=1) + def tls13_should_handle_ClientHello(self): + self.raise_on_packet(TLS13ClientHello, + self.tls13_HANDLED_CLIENTHELLO) + + @ATMT.condition(RECEIVED_CLIENTFLIGHT1, prio=2) def should_handle_ClientHello(self): self.raise_on_packet(TLSClientHello, self.HANDLED_CLIENTHELLO) @@ -223,16 +299,14 @@ def should_check_ciphersuites(self): @ATMT.state() def NO_USABLE_CIPHERSUITE(self): - self.vprint("No usable cipher suite!") raise self.CLOSE_NOTIFY() - @ATMT.condition(RECEIVED_CLIENTFLIGHT1, prio=3) + @ATMT.condition(RECEIVED_CLIENTFLIGHT1, prio=4) def missing_ClientHello(self): raise self.MISSING_CLIENTHELLO() @ATMT.state(final=True) def MISSING_CLIENTHELLO(self): - self.vprint("Missing ClientHello message!") raise self.CLOSE_NOTIFY() @ATMT.state() @@ -290,6 +364,7 @@ def should_add_CertificateRequest(self): self.add_msg(TLSCertificateRequest()) raise self.ADDED_CERTIFICATEREQUEST() + @ATMT.state() def ADDED_CERTIFICATEREQUEST(self): pass @@ -480,6 +555,459 @@ def SENT_SERVERFLIGHT2(self): # end of TLS handshake # + # TLS 1.3 handshake # + @ATMT.state() + def tls13_HANDLED_CLIENTHELLO(self): + s = self.cur_session + m = s.handshake_messages_parsed[-1] + + """ + Check if we have to send an HelloRetryRequest + XXX check also with non ECC groups + """ + if self.curve: + + # We first look for a KeyShareEntry with same groups as self.curve + if not _tls_named_groups[self.curve] in s.tls13_client_pubshares: + + # We then check if self.curve was advertised in SupportedGroups + # extension + for e in m.ext: + if isinstance(e, TLS_Ext_SupportedGroups): + if self.curve in e.groups: + # Here, we need to send an HelloRetryRequest + raise self.tls13_PREPARE_HELLORETRYREQUEST() + + # Here, we received a message after ClientHello + # We check if it's early_data + if self.early_data: + # Before reading the next message wich should be early data + # encrypted, we create an readConnState instance in order + # to decrypt the early_data. + # + # We need to parse again the ClientHello to find the correct + # ciphersuite for the decryption of early_data + # + # XXX Replace this code by a post_dissect() method ? + + verify_ticket = False + for e in m.ext: + + if isinstance(e, TLS_Ext_PreSharedKey_CH): + psk_identity = e.identities[0].identity + obfuscated_age = e.identities[0].obfuscated_ticket_age + binder = e.binders[0].binder + verify_ticket = True + break + + if verify_ticket: + resumption_psk = self.verify_psk_binder(psk_identity, + obfuscated_age, + binder) + if resumption_psk is not None: + s.tls13_psk_secret = resumption_psk + s.compute_tls13_early_secrets() + s.compute_tls13_other_early_secrets() + + cs_cls = _tls_cipher_suites_cls[self.resumed_ciphersuite] + connection_end = s.connection_end + + s.prcs = writeConnState(ciphersuite=cs_cls, + connection_end=connection_end, + tls_version=0x0304) + s.triggered_prcs_commit = True + cets = s.tls13_derived_secrets["client_early_traffic_secret"] # noqa: E501 + s.prcs.tls13_derive_keys(cets) + + self.get_next_msg(0.0, 1) + if not self.buffer_in: + raise self.tls13_PREPARE_SERVERFLIGHT1() + else: + raise self.tls13_HANDLE_EARLY_DATA() + else: + raise self.tls13_PREPARE_SERVERFLIGHT1() + + @ATMT.state() + def tls13_PREPARE_HELLORETRYREQUEST(self): + pass + + @ATMT.condition(tls13_PREPARE_HELLORETRYREQUEST) + def tls13_should_add_HelloRetryRequest(self): + self.add_record(is_tls13=False) + + if isinstance(self.mykey, PrivKeyRSA): + kx = "RSA" + elif isinstance(self.mykey, PrivKeyECDSA): + kx = "ECDSA" + usable_suites = get_usable_ciphersuites(self.cur_pkt.ciphers, kx) + c = usable_suites[0] + + ext = [] + ext += TLS_Ext_SupportedVersion_SH(version="TLS 1.3") + ext += TLS_Ext_KeyShare_HRR(selected_group=_tls_named_groups[self.curve]) # noqa: E501 + + if self.cookie: + ext += TLS_Ext_Cookie() + p = TLS13HelloRetryRequest(cipher=c, ext=ext) + self.add_msg(p) + self.flush_records() + + raise self.tls13_HANDLED_HELLORETRYREQUEST() + + @ATMT.state() + def tls13_HANDLED_HELLORETRYREQUEST(self): + pass + + @ATMT.condition(tls13_HANDLED_HELLORETRYREQUEST) + def tls13_should_add_ServerHello_from_HRR(self): + raise self.WAITING_CLIENTFLIGHT1() + + @ATMT.state() + def tls13_HANDLE_EARLY_DATA(self): + + p = self.buffer_in[0] + self.buffer_in = self.buffer_in[1:] + + # We check that the server can handle early_data and + # that the size of the data received is within the limit + # authorized by the server + # + # If not the case, early_data won't be accepted + if ((self.early_data) and (len(p) < self.max_early_data_size) and + isinstance(p, TLSApplicationData)): + self.vprint("Early data is accepted") + self.early_data_was_accepted = True + + raise self.tls13_PREPARE_SERVERFLIGHT1() + + @ATMT.state() + def tls13_PREPARE_SERVERFLIGHT1(self): + self.add_record(is_tls13=False) + + + # The session_ticket_file contains all the ticket issued from this server + def verify_psk_binder(self, psk_identity, obfuscated_age, binder): + with (self.session_ticket_file, "rb") as f: + for line in f: + s = line.strip().split(b';') + + if len(s) < 8: + continue + + ticket_label = binascii.unhexlify(s[0]) + ticket_nonce = binascii.unhexlify(s[1]) + tmp = binascii.unhexlify(s[2]) + ticket_lifetime = struct.unpack("!I", tmp)[0] + tmp = binascii.unhexlify(s[3]) + ticket_age_add = struct.unpack("!I", tmp)[0] + tmp = binascii.unhexlify(s[4]) + ticket_start_time = struct.unpack("!I", tmp)[0] + resumption_secret = binascii.unhexlify(s[5]) + tmp = binascii.unhexlify(s[6]) + res_ciphersuite = struct.unpack("!H", tmp)[0] + tmp = binascii.unhexlify(s[7]) + max_early_data_size = struct.unpack("!I", tmp)[0] + + # Here psk_identity is a Ticket type but ticket_label is bytes, + # we need to convert psk_identiy to bytes in order to compare + # both strings + if psk_identity.__bytes__() == ticket_label: + + # We compute the resumed PSK associated the resumption + # secret + self.vprint("Ticket found in database !") + if res_ciphersuite not in _tls_cipher_suites_cls: + warning("Unknown cipher suite %d" % res_ciphersuite) + # we do not try to set a default nor stop the execution + else: + cs_cls = _tls_cipher_suites_cls[res_ciphersuite] + + hkdf = TLS13_HKDF(cs_cls.hash_alg.name.lower()) + hash_len = hkdf.hash.digest_size + + tls13_psk_secret = hkdf.expand_label(resumption_secret, + b"resumption", + ticket_nonce, + hash_len) + + # We verify that ticket age is not expired + agesec = int((time.time() - ticket_start_time)) + #agems = agesec * 1000 + #ticket_age = obfuscated_age - ticket_age_add + + # We verify the PSK binder + s = self.cur_session + if s.tls13_retry: + handshake_context = struct.pack("B", 254) + handshake_context += struct.pack("B", 0) + handshake_context += struct.pack("B", 0) + handshake_context += struct.pack("B", hash_len) + digest = hashes.Hash(hkdf.hash, backend=default_backend()) # noqa: E501 + digest.update(s.handshake_messages[0]) + handshake_context += digest.finalize() + + for m in s.handshake_messages[1:]: + if (isinstance(TLS13ClientHello) or + isinstance(TLSClientHello)): + handshake_context += m[:-hash_len - 3] + else: + handshake_context += m + else: + handshake_context = s.handshake_messages[0][:-hash_len-3] # noqa: E501 + + # We compute the binder key + # XXX use the compute_tls13_early_secrets() function + tls13_early_secret = hkdf.extract(None, tls13_psk_secret) + binder_key = hkdf.derive_secret(tls13_early_secret, + b"res binder", + b"") + + computed_binder = hkdf.compute_verify_data(binder_key, + handshake_context) # noqa: E501 + + if (agesec < ticket_lifetime and + computed_binder == binder): + self.vprint("Ticket has been accepted ! ") + self.max_early_data_size = max_early_data_size + self.resumed_ciphersuite = res_ciphersuite + return tls13_psk_secret + + self.vprint("Ticket has not been accepted ! Fallback to a complete handshake") # noqa: E501 + return None + + @ATMT.condition(tls13_PREPARE_SERVERFLIGHT1) + def tls13_should_add_ServerHello(self): + + psk_identity = None + psk_key_exchange_mode = None + obfuscated_age = None + + # XXX check ClientHello extensions... + for m in reversed(self.cur_session.handshake_messages_parsed): + if type(m) == TLS13ClientHello or type(m) == TLSClientHello: + for e in m.ext: + if isinstance(e, TLS_Ext_PreSharedKey_CH): + psk_identity = e.identities[0].identity + obfuscated_age = e.identities[0].obfuscated_ticket_age + binder = e.binders[0].binder + + # For out-of-bound PSK, obfuscated_ticket_age should be + # 0. We use this field to distinguish between out-of- + # bound PSK and resumed PSK + is_out_of_band_psk = (obfuscated_age == 0) + + if isinstance(e, TLS_Ext_PSKKeyExchangeModes): + psk_key_exchange_mode = e.kxmodes[0] + + if isinstance(self.mykey, PrivKeyRSA): + kx = "RSA" + elif isinstance(self.mykey, PrivKeyECDSA): + kx = "ECDSA" + usable_suites = get_usable_ciphersuites(self.cur_pkt.ciphers, kx) + c = usable_suites[0] + group = next(iter(self.cur_session.tls13_client_pubshares)) + + ext = [TLS_Ext_SupportedVersion_SH(version="TLS 1.3")] + + if ((psk_identity is not None and obfuscated_age is not None) and + psk_key_exchange_mode is not None): + s = self.cur_session + if is_out_of_band_psk: + # XXX test that self.psk_secret is set + s.tls13_psk_secret = binascii.unhexlify(self.psk_secret) + # 0: "psk_ke" + # 1: "psk_dhe_ke" + if psk_key_exchange_mode == 1: + server_kse = KeyShareEntry(group=group) + ext += [TLS_Ext_KeyShare_SH(server_share=server_kse)] + + ext += [TLS_Ext_PreSharedKey_SH(selected_identity=0)] + else: + resumption_psk = self.verify_psk_binder(psk_identity, + obfuscated_age, + binder) + + if resumption_psk is None: + # We did not find a ticket matching the one provided in the + # ClientHello. We fallback to a regular 1-RTT handshake + server_kse = KeyShareEntry(group=group) + ext += [TLS_Ext_KeyShare_SH(server_share=server_kse)] + else: + # 0: "psk_ke" + # 1: "psk_dhe_ke" + if psk_key_exchange_mode == 1: + server_kse = KeyShareEntry(group=group) + ext += [TLS_Ext_KeyShare_SH(server_share=server_kse)] + + ext += [TLS_Ext_PreSharedKey_SH(selected_identity=0)] + self.cur_session.tls13_psk_secret = resumption_psk + else: + ext += [TLS_Ext_KeyShare_SH(server_share=KeyShareEntry(group=group))] # noqa: E501 + + if self.cur_session.sid is not None: + p = TLS13ServerHello(cipher=c, sid=self.cur_session.sid, ext=ext) + else: + p = TLS13ServerHello(cipher=c, ext=ext) + self.add_msg(p) + raise self.tls13_ADDED_SERVERHELLO() + + @ATMT.state() + def tls13_ADDED_SERVERHELLO(self): + pass + + @ATMT.condition(tls13_ADDED_SERVERHELLO) + def tls13_should_add_EncryptedExtensions(self): + self.add_record(is_tls13=True) + if self.early_data_was_accepted: + p = TLSEncryptedExtensions(ext=TLS_Ext_EarlyDataIndication()) + self.add_msg(p) + else: + self.add_msg(TLSEncryptedExtensions(extlen=0)) + raise self.tls13_ADDED_ENCRYPTEDEXTENSIONS() + + @ATMT.state() + def tls13_ADDED_ENCRYPTEDEXTENSIONS(self): + pass + + @ATMT.condition(tls13_ADDED_ENCRYPTEDEXTENSIONS) + def tls13_should_add_CertificateRequest(self): + if self.client_auth: + ext = [TLS_Ext_SignatureAlgorithms(sig_algs=["sha256+rsaepss"])] + p = TLS13CertificateRequest(ext=ext) + self.add_msg(p) + raise self.tls13_ADDED_CERTIFICATEREQUEST() + + @ATMT.state() + def tls13_ADDED_CERTIFICATEREQUEST(self): + pass + + @ATMT.condition(tls13_ADDED_CERTIFICATEREQUEST) + def tls13_should_add_Certificate(self): + # If a PSK is set, an extension pre_shared_key + # was send in the ServerHello. No certificate should + # be send here + if not self.cur_session.tls13_psk_secret: + certs = [] + for c in self.cur_session.server_certs: + certs += _ASN1CertAndExt(cert=c) + + self.add_msg(TLS13Certificate(certs=certs)) + raise self.tls13_ADDED_CERTIFICATE() + + @ATMT.state() + def tls13_ADDED_CERTIFICATE(self): + pass + + @ATMT.condition(tls13_ADDED_CERTIFICATE) + def tls13_should_add_CertificateVerifiy(self): + if not self.cur_session.tls13_psk_secret: + self.add_msg(TLSCertificateVerify()) + raise self.tls13_ADDED_CERTIFICATEVERIFY() + + @ATMT.state() + def tls13_ADDED_CERTIFICATEVERIFY(self): + pass + + @ATMT.condition(tls13_ADDED_CERTIFICATEVERIFY) + def tls13_should_add_Finished(self): + self.add_msg(TLSFinished()) + raise self.tls13_ADDED_SERVERFINISHED() + + @ATMT.state() + def tls13_ADDED_SERVERFINISHED(self): + pass + + @ATMT.condition(tls13_ADDED_SERVERFINISHED) + def tls13_should_send_ServerFlight1(self): + self.flush_records() + raise self.tls13_WAITING_CLIENTFLIGHT2() + + @ATMT.state() + def tls13_WAITING_CLIENTFLIGHT2(self): + self.get_next_msg() + raise self.tls13_RECEIVED_CLIENTFLIGHT2() + + @ATMT.state() + def tls13_RECEIVED_CLIENTFLIGHT2(self): + pass + + @ATMT.condition(tls13_RECEIVED_CLIENTFLIGHT2, prio=1) + def tls13_should_handle_ClientFlight2(self): + if self.client_auth: + self.raise_on_packet(TLS13Certificate, + self.TLS13_HANDLED_CLIENTCERTIFICATE) + elif self.early_data_was_accepted: + self.raise_on_packet(TLS13EndOfEarlyData, + self.TLS13_HANDLED_ENDOFEARLYDATA) + else: + self.raise_on_packet(TLSFinished, + self.TLS13_HANDLED_CLIENTFINISHED) + + @ATMT.condition(tls13_RECEIVED_CLIENTFLIGHT2, prio=2) + def tls13_should_handle_ClientFinished(self): + self.raise_on_packet(TLSFinished, + self.TLS13_HANDLED_CLIENTFINISHED) + + @ATMT.state() + def TLS13_HANDLED_ENDOFEARLYDATA(self): + pass + + @ATMT.condition(TLS13_HANDLED_ENDOFEARLYDATA) + def tls13_should_handle_ClientFinishedFromEndOfEarlyData(self): + self.raise_on_packet(TLSFinished, + self.TLS13_HANDLED_CLIENTFINISHED) + + # RFC8446, section 4.4.2.4 : + # "If the client does not send any certificates (i.e., it sends an empty + # Certificate message), the server MAY at its discretion either + # continue the handshake without client authentication or abort the + # handshake with a "certificate_required" alert." + # Here, we abort the handshake. + @ATMT.state() + def TLS13_HANDLED_CLIENTCERTIFICATE(self): + if self.client_auth: + self.vprint("Received client certificate chain...") + self.vprint(self.cur_pkt.show()) + if isinstance(self.cur_pkt, TLS13Certificate): + if self.cur_pkt.certslen == 0: + raise self.TLS13_MISSING_CLIENTCERTIFICATE() + + @ATMT.condition(TLS13_HANDLED_CLIENTCERTIFICATE) + def tls13_should_handle_ClientCertificateVerify(self): + self.raise_on_packet(TLSCertificateVerify, + self.TLS13_HANDLED_CLIENT_CERTIFICATEVERIFY) + + @ATMT.condition(TLS13_HANDLED_CLIENTCERTIFICATE, prio=2) + def tls13_no_Client_CertificateVerify(self): + if self.client_auth: + raise self.TLS13_MISSING_CLIENTCERTIFICATE() + raise self.TLS13_HANDLED_CLIENT_CERTIFICATEVERIFY() + + # TODO : change alert code + @ATMT.state() + def TLS13_MISSING_CLIENTCERTIFICATE(self): + self.vprint("Missing ClientCertificate!") + raise self.CLOSE_NOTIFY() + + @ATMT.state() + def TLS13_HANDLED_CLIENT_CERTIFICATEVERIFY(self): + pass + + @ATMT.condition(TLS13_HANDLED_CLIENT_CERTIFICATEVERIFY) + def tls13_should_handle_ClientFinished(self): + self.raise_on_packet(TLSFinished, + self.TLS13_HANDLED_CLIENTFINISHED) + + @ATMT.state() + def TLS13_HANDLED_CLIENTFINISHED(self): + self.vprint("TLS handshake completed!") + self.vprint_sessioninfo() + if self.is_echo_server: + self.vprint("Will now act as a simple echo server.") + raise self.WAITING_CLIENTDATA() + + # end of TLS 1.3 handshake # + @ATMT.state() def WAITING_CLIENTDATA(self): self.get_next_msg(self.max_client_idle_time, 1) @@ -489,6 +1017,39 @@ def WAITING_CLIENTDATA(self): def RECEIVED_CLIENTDATA(self): pass + def save_ticket(self, ticket): + + if (not isinstance(ticket, TLS13NewSessionTicket) or + self.session_ticket_file is None): + return + + s = self.cur_session + with open(self.session_ticket_file, "ab") as f: + # ticket;ticket_nonce;obfuscated_age;start_time;resumption_secret + line = binascii.hexlify(ticket.ticket) + line += b";" + line += binascii.hexlify(ticket.ticket_nonce) + line += b";" + line += binascii.hexlify(struct.pack("!I", ticket.ticket_lifetime)) + lien += b";" + line += binascii.hexlify(struct.pack("!I", ticket.ticket_age_add)) + line += b";" + line += binascii.hexlify(struct.pack("!I", int(time.time()))) + line += b";" + line += binascii.hexlify(s.tls13_derived_secrets["resumption_secret"]) # noqa: E501 + line += b";" + line += binascii.hexlify(struct.pack("!H", s.wcs.ciphersuite.val)) + line += b";" + if (ticket.ext is None or ticket.extlen == 0): + line += binascii.hexlify(struct.pack("!I", 0)) + else: + for e in ticket.ext: + if isinstance(e, TLS_Ext_EarlyDataIndicationTicket): + max_size = struct.pack("!I", e.max_early_data_size) + line += binascii.hexlify(max_early_data_size) + line += b"\n" + f.write(line) + @ATMT.condition(RECEIVED_CLIENTDATA) def should_handle_ClientData(self): if not self.buffer_in: @@ -508,6 +1069,13 @@ def should_handle_ClientData(self): elif isinstance(p, TLSAlert): print("> Received: %r" % p) raise self.CLOSE_NOTIFY() + + elif isinstance(p, TLS13KeyUpdate): + print("> Received: %r" % p) + p = TLS13KeyUpdate(request_update=0) + self.add_record() + self.add_msg(p) + raise self.ADDED_SERVERDATA() else: print("> Received: %r" % p) @@ -517,6 +1085,15 @@ def should_handle_ClientData(self): if self.is_echo_server or recv_data.startswith(b"GET / HTTP/1.1"): self.add_record() self.add_msg(p) + + if self.handle_session_ticket: + self.add_record() + if self.early_data: + ext = TLS_Ext_EarlyDataIndicationTicket(max_early_data_size=16384) # noqa: E501 + ticket = TLS13NewSessionTicket(ext=[ext]) + else: + ticket = TLS13NewSessionTicket(ext=[]) + self.add_msg(ticket) raise self.ADDED_SERVERDATA() raise self.HANDLED_CLIENTDATA() @@ -531,7 +1108,27 @@ def ADDED_SERVERDATA(self): @ATMT.condition(ADDED_SERVERDATA) def should_send_ServerData(self): + if self.session_ticket_file: + save_ticket = False + for p in self.buffer_out: + if isinstance(p, TLS13): + # Check if there's a NewSessionTicket to send + save_ticket = all(map(lambda x: isinstance(x, TLS13NewSessionTicket), # noqa: E501 + p.inner.msg)) + if save_ticket: + break + self.flush_records() + + if self.session_ticket_file and save_ticket: + # Loop backward in message send to retrieve the parsed + # NewSessionTicket. This message is not completly build before the + # flush_records() call. Other way to build this message before ? + for p in reversed(self.cur_session.handshake_messages_parsed): + if isinstance(p, TLS13NewSessionTicket): + self.save_ticket(p) + break + raise self.SENT_SERVERDATA() @ATMT.state() @@ -575,7 +1172,7 @@ def close_session_final(self): # SSLv2 handshake # - @ATMT.condition(RECEIVED_CLIENTFLIGHT1, prio=2) + @ATMT.condition(RECEIVED_CLIENTFLIGHT1, prio=3) def sslv2_should_handle_ClientHello(self): self.raise_on_packet(SSLv2ClientHello, self.SSLv2_HANDLED_CLIENTHELLO) diff --git a/scapy/layers/tls/cert.py b/scapy/layers/tls/cert.py index dafca992801..f57c682ba7c 100644 --- a/scapy/layers/tls/cert.py +++ b/scapy/layers/tls/cert.py @@ -673,7 +673,10 @@ def encrypt(self, msg, t="pkcs", h="sha256", mgf=None, L=None): return self.pubKey.encrypt(msg, t=t, h=h, mgf=mgf, L=L) def verify(self, msg, sig, t="pkcs", h="sha256", mgf=None, L=None): - return self.pubKey.verify(msg, sig, t=t, h=h, mgf=mgf, L=L) + if isinstance(self.pubKey, PubKeyECDSA): + return self.pubKey.verify(msg, sig) + else: + return self.pubKey.verify(msg, sig, t=t, h=h, mgf=mgf, L=L) def remainingDays(self, now=None): """ diff --git a/scapy/layers/tls/crypto/cipher_aead.py b/scapy/layers/tls/crypto/cipher_aead.py index 99b77712e0c..83be72e4300 100644 --- a/scapy/layers/tls/crypto/cipher_aead.py +++ b/scapy/layers/tls/crypto/cipher_aead.py @@ -286,7 +286,11 @@ def __init__(self, key=None, fixed_iv=None, nonce_explicit=None): self.pc_cls_mode(fixed_iv), backend=default_backend()) else: - self._cipher = self.cipher_cls(key) + if self.cipher_cls == ChaCha20Poly1305: + # ChaCha20Poly1305 doesn't have a tag_length argument... + self._cipher = self.cipher_cls(key) + else: + self._cipher = self.cipher_cls(key, tag_length=self.tag_len) def __setattr__(self, name, val): if name == "key": @@ -322,12 +326,12 @@ def auth_encrypt(self, P, A, seq_num): encryptor = self._cipher.encryptor() encryptor.authenticate_additional_data(A) res = encryptor.update(P) + encryptor.finalize() + res += encryptor.tag else: if (conf.crypto_valid_advanced and isinstance(self._cipher, AESCCM)): - res = self._cipher.encrypt(self._get_nonce(seq_num), P, A, - tag_length=self.tag_len) + res = self._cipher.encrypt(self._get_nonce(seq_num), P, A) else: res = self._cipher.encrypt(self._get_nonce(seq_num), P, A) return res @@ -341,25 +345,24 @@ def auth_decrypt(self, A, C, seq_num): raise a CipherError which contains the encrypted input. """ C, mac = C[:-self.tag_len], C[-self.tag_len:] + if False in six.itervalues(self.ready): raise CipherError(C, mac) if hasattr(self, "pc_cls"): self._cipher.mode._initialization_vector = self._get_nonce(seq_num) - self._cipher.mode._tag = mac decryptor = self._cipher.decryptor() decryptor.authenticate_additional_data(A) P = decryptor.update(C) try: - decryptor.finalize() + decryptor.finalize_with_tag(mac) except InvalidTag: raise AEADTagError(P, mac) else: try: if (conf.crypto_valid_advanced and isinstance(self._cipher, AESCCM)): - P = self._cipher.decrypt(self._get_nonce(seq_num), C + mac, A, # noqa: E501 - tag_length=self.tag_len) + P = self._cipher.decrypt(self._get_nonce(seq_num), C + mac, A) # noqa: E501 else: if (conf.crypto_valid_advanced and isinstance(self, Cipher_CHACHA20_POLY1305)): @@ -412,6 +415,7 @@ class Cipher_AES_128_CCM_TLS13(_AEADCipher_TLS13): cipher_cls = AESCCM key_len = 16 tag_len = 16 + fixed_iv_len = 12 class Cipher_AES_128_CCM_8_TLS13(Cipher_AES_128_CCM_TLS13): tag_len = 8 diff --git a/scapy/layers/tls/crypto/hkdf.py b/scapy/layers/tls/crypto/hkdf.py index 5cad7823e39..f9c69c49312 100644 --- a/scapy/layers/tls/crypto/hkdf.py +++ b/scapy/layers/tls/crypto/hkdf.py @@ -36,8 +36,8 @@ def expand(self, prk, info, L): def expand_label(self, secret, label, hash_value, length): hkdf_label = struct.pack("!H", length) - hkdf_label += struct.pack("B", 9 + len(label)) - hkdf_label += b"TLS 1.3, " + hkdf_label += struct.pack("B", 6 + len(label)) + hkdf_label += b"tls13 " hkdf_label += label hkdf_label += struct.pack("B", len(hash_value)) hkdf_label += hash_value diff --git a/scapy/layers/tls/crypto/suites.py b/scapy/layers/tls/crypto/suites.py index 2f6eeff6dd1..31b52bcd1aa 100644 --- a/scapy/layers/tls/crypto/suites.py +++ b/scapy/layers/tls/crypto/suites.py @@ -23,6 +23,7 @@ def get_algs_from_ciphersuite_name(ciphersuite_name): Return the 3-tuple made of the Key Exchange Algorithm class, the Cipher class and the HMAC class, through the parsing of the ciphersuite name. """ + tls1_3 = False if ciphersuite_name.startswith("TLS"): s = ciphersuite_name[4:] @@ -1312,6 +1313,8 @@ def get_usable_ciphersuites(l, kx): if ciph.usable: # XXX select among RSA and ECDSA cipher suites # according to the key(s) the server was given - if ciph.kx_alg.anonymous or kx in ciph.kx_alg.name: + if (ciph.kx_alg.anonymous or + kx in ciph.kx_alg.name or + ciph.kx_alg.name == "TLS13"): res.append(c) return res diff --git a/scapy/layers/tls/extensions.py b/scapy/layers/tls/extensions.py index ea56a5e8bd0..d26f33683e9 100644 --- a/scapy/layers/tls/extensions.py +++ b/scapy/layers/tls/extensions.py @@ -5,10 +5,13 @@ """ TLS handshake extensions. """ +import struct +import os -from __future__ import print_function +#from __future__ import print_function -import struct +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import hashes from scapy.fields import ByteEnumField, ByteField, EnumField, FieldLenField, \ FieldListField, IntField, PacketField, PacketListField, ShortEnumField, \ @@ -18,13 +21,13 @@ from scapy.layers.tls.basefields import _tls_version from scapy.layers.tls.keyexchange import (SigAndHashAlgsLenField, SigAndHashAlgsField, _tls_hash_sig) + from scapy.layers.tls.session import _GenericTLSSessionInheritance from scapy.layers.tls.crypto.groups import _tls_named_groups from scapy.themes import AnsiColorTheme from scapy.compat import raw from scapy.config import conf - _tls_ext = {0: "server_name", # RFC 4366 1: "max_fragment_length", # RFC 4366 2: "client_certificate_url", # RFC 4366 @@ -42,23 +45,29 @@ 0x0f: "heartbeat", # RFC 6520 0x10: "alpn", # RFC 7301 0x12: "signed_certificate_timestamp", # RFC 6962 + 0x13: "client_certificate_type", # RFC 7250 + 0x14: "server_certificate_type", # RFC 7250 0x15: "padding", # RFC 7685 0x16: "encrypt_then_mac", # RFC 7366 0x17: "extended_master_secret", # RFC 7627 + 0x1c: "record_size_limit", # RFC 8449 0x23: "session_ticket", # RFC 5077 - 0x28: "key_share", + # 0x28: "key_share", 0x29: "pre_shared_key", - 0x2a: "early_data", + 0x2a: "early_data_indication", 0x2b: "supported_versions", 0x2c: "cookie", 0x2d: "psk_key_exchange_modes", - 0x2e: "ticket_early_data_info", + # 0x2e: "ticket_early_data_info", 0x2f: "certificate_authorities", 0x30: "oid_filters", + 0x31: "post_handshake_auth", + 0x32: "signature_algorithms_cert", + 0x33: "key_share", 0x3374: "next_protocol_negotiation", # RFC-draft-agl-tls-nextprotoneg-03 0xff01: "renegotiation_info" # RFC 5746 - } + } class TLS_Ext_Unknown(_GenericTLSSessionInheritance): @@ -72,11 +81,11 @@ class TLS_Ext_Unknown(_GenericTLSSessionInheritance): StrLenField("val", "", length_from=lambda pkt: pkt.len)] - def post_build(self, p, pay): + def post_build(self, pkt, pay): if self.len is None: - tmp_len = len(p) - 4 - p = p[:2] + struct.pack("!H", tmp_len) + p[4:] - return p + pay + tmp_len = len(pkt) - 4 + pkt = pkt[:2] + struct.pack("!H", tmp_len) + pkt[4:] + return pkt + pay ############################################################################### @@ -145,7 +154,7 @@ class ServerName(Packet): StrLenField("servername", "", length_from=lambda pkt: pkt.namelen)] - def guess_payload_class(self, p): + def guess_payload_class(self, payload): return Padding @@ -204,7 +213,7 @@ class TAPreAgreed(Packet): name = "Trusted authority - pre_agreed" fields_desc = [ByteEnumField("idtype", 0, _tls_trusted_authority_types)] - def guess_payload_class(self, p): + def guess_payload_class(self, payload): return Padding @@ -213,7 +222,7 @@ class TAKeySHA1Hash(Packet): fields_desc = [ByteEnumField("idtype", 1, _tls_trusted_authority_types), StrFixedLenField("id", None, 20)] - def guess_payload_class(self, p): + def guess_payload_class(self, payload): return Padding @@ -227,7 +236,7 @@ class TAX509Name(Packet): FieldLenField("dnlen", None, length_of="dn"), StrLenField("dn", "", length_from=lambda pkt: pkt.dnlen)] - def guess_payload_class(self, p): + def guess_payload_class(self, payload): return Padding @@ -236,7 +245,7 @@ class TACertSHA1Hash(Packet): fields_desc = [ByteEnumField("idtype", 3, _tls_trusted_authority_types), StrFixedLenField("id", None, 20)] - def guess_payload_class(self, p): + def guess_payload_class(self, payload): return Padding @@ -281,7 +290,7 @@ class ResponderID(Packet): StrLenField("respid", "", length_from=lambda pkt: pkt.respidlen)] - def guess_payload_class(self, p): + def guess_payload_class(self, payload): return Padding @@ -296,7 +305,7 @@ class OCSPStatusRequest(Packet): FieldLenField("reqextlen", None, length_of="reqext"), PacketField("reqext", "", X509_Extensions)] - def guess_payload_class(self, p): + def guess_payload_class(self, payload): return Padding @@ -337,7 +346,7 @@ class TLS_Ext_ClientAuthz(TLS_Ext_Unknown): # RFC 5878 name = "TLS Extension - Client Authz" fields_desc = [ShortEnumField("type", 7, _tls_ext), ShortField("len", None), - ] + ] class TLS_Ext_ServerAuthz(TLS_Ext_Unknown): # RFC 5878 @@ -345,7 +354,7 @@ class TLS_Ext_ServerAuthz(TLS_Ext_Unknown): # RFC 5878 name = "TLS Extension - Server Authz" fields_desc = [ShortEnumField("type", 8, _tls_ext), ShortField("len", None), - ] + ] _tls_cert_types = {0: "X.509", 1: "OpenPGP"} @@ -444,7 +453,7 @@ class ProtocolName(Packet): StrLenField("protocol", "", length_from=lambda pkt: pkt.len)] - def guess_payload_class(self, p): + def guess_payload_class(self, payload): return Padding @@ -460,7 +469,7 @@ class TLS_Ext_ALPN(TLS_Ext_PrettyPacketList): # RFC 7301 ShortField("len", None), FieldLenField("protocolslen", None, length_of="protocols"), ProtocolListField("protocols", [], ProtocolName, - length_from=lambda pkt:pkt.protocolslen)] + length_from=lambda pkt: pkt.protocolslen)] class TLS_Ext_Padding(TLS_Ext_Unknown): # RFC 7685 @@ -507,14 +516,32 @@ class TLS_Ext_PreSharedKey(TLS_Ext_Unknown): ShortField("len", None)] -class TLS_Ext_EarlyData(TLS_Ext_Unknown): +class TLS_Ext_EarlyDataIndication(TLS_Ext_Unknown): name = "TLS Extension - Early Data" fields_desc = [ShortEnumField("type", 0x2a, _tls_ext), ShortField("len", None)] +class TLS_Ext_EarlyDataIndicationTicket(TLS_Ext_Unknown): + name = "TLS Extension - Ticket Early Data Info" + fields_desc = [ShortEnumField("type", 0x2a, _tls_ext), + ShortField("len", None), + IntField("max_early_data_size", 0)] + + +_tls_ext_early_data_cls = {1: TLS_Ext_EarlyDataIndication, + 4: TLS_Ext_EarlyDataIndicationTicket, + 8: TLS_Ext_EarlyDataIndication} + + class TLS_Ext_SupportedVersions(TLS_Ext_Unknown): - name = "TLS Extension - Supported Versions" + name = "TLS Extension - Supported Versions (dummy class)" + fields_desc = [ShortEnumField("type", 0x2b, _tls_ext), + ShortField("len", None)] + + +class TLS_Ext_SupportedVersion_CH(TLS_Ext_Unknown): + name = "TLS Extension - Supported Versions (for ClientHello)" fields_desc = [ShortEnumField("type", 0x2b, _tls_ext), ShortField("len", None), FieldLenField("versionslen", None, fmt='B', @@ -525,6 +552,22 @@ class TLS_Ext_SupportedVersions(TLS_Ext_Unknown): length_from=lambda pkt: pkt.versionslen)] +class TLS_Ext_SupportedVersion_SH(TLS_Ext_Unknown): + name = "TLS Extension - Supported Versions (for ServerHello)" + fields_desc = [ShortEnumField("type", 0x2b, _tls_ext), + ShortField("len", None), + ShortEnumField("version", None, _tls_version)] + + def post_dissection(self, r): + if isinstance(r, TLS_Ext_SupportedVersion_SH): + self.tls_session.tls_version = r.version + return super(TLS_Ext_SupportedVersion_SH, self).post_dissection(r) + + +_tls_ext_supported_version_cls = {1: TLS_Ext_SupportedVersion_CH, + 2: TLS_Ext_SupportedVersion_SH} + + class TLS_Ext_Cookie(TLS_Ext_Unknown): name = "TLS Extension - Cookie" fields_desc = [ShortEnumField("type", 0x2c, _tls_ext), @@ -533,6 +576,13 @@ class TLS_Ext_Cookie(TLS_Ext_Unknown): XStrLenField("cookie", "", length_from=lambda pkt: pkt.cookielen)] + def build(self): + fval = self.getfieldval("cookie") + if fval is None or fval == b"": + self.cookie = os.urandom(32) + + return TLS_Ext_Unknown.build(self) + _tls_psk_kx_modes = {0: "psk_ke", 1: "psk_dhe_ke"} @@ -549,13 +599,6 @@ class TLS_Ext_PSKKeyExchangeModes(TLS_Ext_Unknown): length_from=lambda pkt: pkt.kxmodeslen)] -class TLS_Ext_TicketEarlyDataInfo(TLS_Ext_Unknown): - name = "TLS Extension - Ticket Early Data Info" - fields_desc = [ShortEnumField("type", 0x2e, _tls_ext), - ShortField("len", None), - IntField("max_early_data_size", 0)] - - class TLS_Ext_NPN(TLS_Ext_PrettyPacketList): """ Defined in RFC-draft-agl-tls-nextprotoneg-03. Deprecated in favour of ALPN. @@ -564,7 +607,7 @@ class TLS_Ext_NPN(TLS_Ext_PrettyPacketList): fields_desc = [ShortEnumField("type", 0x3374, _tls_ext), FieldLenField("len", None, length_of="protocols"), ProtocolListField("protocols", [], ProtocolName, - length_from=lambda pkt:pkt.len)] + length_from=lambda pkt: pkt.len)] class TLS_Ext_RenegotiationInfo(TLS_Ext_Unknown): # RFC 5746 @@ -577,6 +620,30 @@ class TLS_Ext_RenegotiationInfo(TLS_Ext_Unknown): # RFC 5746 length_from=lambda pkt: pkt.reneg_conn_len)] +class TLS_Ext_PostHandshakeAuth(TLS_Ext_Unknown): # RFC 8446 + name = "TLS Extension - Post Handshake Auth" + fields_desc = [ShortEnumField("type", 0x31, _tls_ext), + ShortField("len", None)] + + +class TLS_Ext_SignatureAlgorithmsCert(TLS_Ext_Unknown): # RFC 8446 + name = "TLS Extension - Signature Algorithms Cert" + fields_desc = [ShortEnumField("type", 0x31, _tls_ext), + ShortField("len", None), + SigAndHashAlgsLenField("sig_algs_len", None, + length_of="sig_algs"), + SigAndHashAlgsField("sig_algs", [], + EnumField("hash_sig", None, + _tls_hash_sig), + length_from=lambda pkt: pkt.sig_algs_len)] # noqa: E501 + + +class TLS_Ext_RecordSizeLimit(TLS_Ext_Unknown): # RFC 8449 + name = "TLS Extension - Record Size Limit" + fields_desc = [ShortEnumField("type", 0x1c, _tls_ext), + ShortField("len", None), + ShortField("record_size_limit", None)] + _tls_ext_cls = {0: TLS_Ext_ServerName, 1: TLS_Ext_MaxFragLen, 2: TLS_Ext_ClientCertURL, @@ -596,19 +663,23 @@ class TLS_Ext_RenegotiationInfo(TLS_Ext_Unknown): # RFC 5746 0x15: TLS_Ext_Padding, 0x16: TLS_Ext_EncryptThenMAC, 0x17: TLS_Ext_ExtendedMasterSecret, + 0x1c: TLS_Ext_RecordSizeLimit, 0x23: TLS_Ext_SessionTicket, - 0x28: TLS_Ext_KeyShare, + # 0x28: TLS_Ext_KeyShare, 0x29: TLS_Ext_PreSharedKey, - 0x2a: TLS_Ext_EarlyData, + 0x2a: TLS_Ext_EarlyDataIndication, 0x2b: TLS_Ext_SupportedVersions, 0x2c: TLS_Ext_Cookie, 0x2d: TLS_Ext_PSKKeyExchangeModes, - 0x2e: TLS_Ext_TicketEarlyDataInfo, + # 0x2e: TLS_Ext_TicketEarlyDataInfo, + 0x31: TLS_Ext_PostHandshakeAuth, + 0x32: TLS_Ext_SignatureAlgorithmsCert, + 0x33: TLS_Ext_KeyShare, # 0x2f: TLS_Ext_CertificateAuthorities, #XXX # 0x30: TLS_Ext_OIDFilters, #XXX 0x3374: TLS_Ext_NPN, 0xff01: TLS_Ext_RenegotiationInfo - } + } class _ExtensionsLenField(FieldLenField): @@ -621,6 +692,7 @@ def getfield(self, pkt, s): """ ext = pkt.get_field(self.length_of) tmp_len = ext.length_from(pkt) + if tmp_len is None or tmp_len <= 0: v = pkt.tls_session.tls_version if v is None or v < 0x0304: @@ -684,16 +756,48 @@ def i2m(self, pkt, i): def m2i(self, pkt, m): res = [] - while len(m) > 4: + while len(m) >= 4: t = struct.unpack("!H", m[:2])[0] tmp_len = struct.unpack("!H", m[2:4])[0] cls = _tls_ext_cls.get(t, TLS_Ext_Unknown) + if cls is TLS_Ext_KeyShare: - from scapy.layers.tls.keyexchange_tls13 import _tls_ext_keyshare_cls # noqa: E501 - cls = _tls_ext_keyshare_cls.get(pkt.msgtype, TLS_Ext_Unknown) + + # TLS_Ext_KeyShare can be : + # - TLS_Ext_KeyShare_CH if the message is a ClientHello + # - TLS_Ext_KeyShare_SH if the message is a ServerHello + # and all parameters are accepted by the serveur + # - TLS_Ext_KeyShare_HRR if message is a ServerHello and + # the client has not provided a sufficient "key_share" + # extension + + from scapy.layers.tls.keyexchange_tls13 import ( + _tls_ext_keyshare_cls, _tls_ext_keyshare_hrr_cls) + + # Because ServerHello and HelloRetryRequest have the same + # msg_type, the only way to distinguish these message is by + # checking the random_bytes. If the random_bytes are equal to + # SHA256('HelloRetryRequest') then we know this is a + # HelloRetryRequest and the TLS_Ext_KeyShare must be parsed as + # TLS_Ext_KeyShare_HRR and not as TLS_Ext_KeyShare_SH + + # XXX Precompute the hash + digest = hashes.Hash(hashes.SHA256(), backend=default_backend()) # noqa: E501 + digest.update(b"HelloRetryRequest") + digest_out = digest.finalize() + + if pkt.random_bytes and pkt.random_bytes == digest_out: + cls = _tls_ext_keyshare_hrr_cls.get(pkt.msgtype, TLS_Ext_Unknown) # noqa: E501 + else: + cls = _tls_ext_keyshare_cls.get(pkt.msgtype, TLS_Ext_Unknown) # noqa: E501 + elif cls is TLS_Ext_PreSharedKey: from scapy.layers.tls.keyexchange_tls13 import _tls_ext_presharedkey_cls # noqa: E501 cls = _tls_ext_presharedkey_cls.get(pkt.msgtype, TLS_Ext_Unknown) # noqa: E501 + elif cls is TLS_Ext_SupportedVersions: + cls = _tls_ext_supported_version_cls.get(pkt.msgtype, TLS_Ext_Unknown) # noqa: E501 + elif cls is TLS_Ext_EarlyDataIndication: + cls = _tls_ext_early_data_cls.get(pkt.msgtype, TLS_Ext_Unknown) res.append(cls(m[:tmp_len + 4], tls_session=pkt.tls_session)) m = m[tmp_len + 4:] return res diff --git a/scapy/layers/tls/handshake.py b/scapy/layers/tls/handshake.py index 79c0650e120..0e6e363aa48 100644 --- a/scapy/layers/tls/handshake.py +++ b/scapy/layers/tls/handshake.py @@ -13,27 +13,39 @@ from __future__ import absolute_import import math import struct +import os -from scapy.error import log_runtime, warning +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import hashes + +from scapy.error import warning, log_runtime from scapy.fields import ByteEnumField, ByteField, EnumField, Field, \ FieldLenField, IntField, PacketField, PacketListField, ShortField, \ StrFixedLenField, StrLenField, ThreeBytesField, UTCTimeField -from scapy.compat import bytes_hex, orb, raw +from scapy.compat import hex_bytes, bytes_hex, orb, raw from scapy.config import conf from scapy.modules import six from scapy.packet import Packet, Raw, Padding -from scapy.utils import randstring, repr_hex +from scapy.utils import repr_hex from scapy.layers.x509 import OCSP_Response from scapy.layers.tls.cert import Cert from scapy.layers.tls.basefields import (_tls_version, _TLSVersionField, _TLSClientVersionField) from scapy.layers.tls.extensions import (_ExtensionsLenField, _ExtensionsField, - _cert_status_type, TLS_Ext_SupportedVersions) # noqa: E501 + _cert_status_type, + TLS_Ext_SupportedVersion_CH, + TLS_Ext_SupportedVersion_SH, + TLS_Ext_SignatureAlgorithms, + TLS_Ext_EarlyDataIndication) + from scapy.layers.tls.keyexchange import (_TLSSignature, _TLSServerParamsField, _TLSSignatureField, ServerRSAParams, SigAndHashAlgsField, _tls_hash_sig, SigAndHashAlgsLenField) + +from scapy.layers.tls.keyexchange_tls13 import (TLS_Ext_PreSharedKey_CH) + from scapy.layers.tls.session import (_GenericTLSSessionInheritance, readConnState, writeConnState) from scapy.layers.tls.crypto.compression import (_tls_compression_algs, @@ -45,6 +57,7 @@ _GenericCipherSuite, _GenericCipherSuiteMetaclass) +from scapy.layers.tls.crypto.hkdf import TLS13_HKDF ############################################################################### # Generic TLS Handshake message # @@ -72,14 +85,14 @@ class _TLSHandshake(_GenericTLSSessionInheritance): StrLenField("msg", "", length_from=lambda pkt: pkt.msglen)] - def post_build(self, p, pay): - tmp_len = len(p) + def post_build(self, pkt, pay): + tmp_len = len(pkt) if self.msglen is None: - l2 = tmp_len - 4 - p = struct.pack("!I", (orb(p[0]) << 24) | l2) + p[4:] - return p + pay + sz = tmp_len - 4 + pkt = struct.pack("!I", (orb(pkt[0]) << 24) | sz) + pkt[4:] + return pkt + pay - def guess_payload_class(self, p): + def guess_payload_class(self, payload): return conf.padding_layer def tls_session_update(self, msg_str): @@ -124,10 +137,10 @@ def i2h(self, pkt, x): class _TLSRandomBytesField(StrFixedLenField): - def i2repr(self, pkt, x): - if x is None: - return repr(x) - return repr_hex(self.i2h(pkt, x)) + def i2repr(self, pkt, v): + if v is None: + return repr(v) + return repr_hex(self.i2h(pkt, v)) class _SessionIDField(StrLenField): @@ -152,10 +165,9 @@ def __init__(self, name, default, dico, length_from=None, itemfmt="!H"): s2i[dico[k]] = k def any2i_one(self, pkt, x): - if (isinstance(x, _GenericCipherSuite) or - isinstance(x, _GenericCipherSuiteMetaclass)): + if isinstance(x, (_GenericCipherSuite, _GenericCipherSuiteMetaclass)): x = x.val - if isinstance(x, bytes): + elif isinstance(x, bytes): x = self.s2i[x] return x @@ -180,30 +192,29 @@ def i2repr(self, pkt, x): tmp_len = "[%s]" % ", ".join(tmp_len) return tmp_len - def i2m(self, pkt, val): - if val is None: - val = [] - return b"".join(struct.pack(self.itemfmt, x) for x in val) + def i2m(self, pkt, x): + if x is None: + x = [] + return b"".join(struct.pack(self.itemfmt, z) for z in x) - def m2i(self, pkt, m): + def m2i(self, pkt, x): res = [] itemlen = struct.calcsize(self.itemfmt) - while m: - res.append(struct.unpack(self.itemfmt, m[:itemlen])[0]) - m = m[itemlen:] + while x: + res.append(struct.unpack(self.itemfmt, x[:itemlen])[0]) + x = x[itemlen:] return res - def i2len(self, pkt, i): - if i is None: + def i2len(self, pkt, x): + if x is None: return 0 - return len(i) * self.itemsize + return len(x) * self.itemsize class _CompressionMethodsField(_CipherSuitesField): def any2i_one(self, pkt, x): - if (isinstance(x, _GenericComp) or - isinstance(x, _GenericCompMetaclass)): + if isinstance(x, (_GenericComp, _GenericCompMetaclass)): x = x.val if isinstance(x, str): x = self.s2i[x] @@ -235,7 +246,7 @@ class TLSClientHello(_TLSHandshake): FieldLenField("sidlen", None, fmt="B", length_of="sid"), _SessionIDField("sid", "", - length_from=lambda pkt:pkt.sidlen), + length_from=lambda pkt: pkt.sidlen), FieldLenField("cipherslen", None, fmt="!H", length_of="ciphers"), @@ -257,25 +268,28 @@ class TLSClientHello(_TLSHandshake): (pkt.complen or 0) - # noqa: E501 40))] - def post_build(self, p, pay): + def post_build(self, pkt, pay): if self.random_bytes is None: - p = p[:10] + randstring(28) + p[10 + 28:] + # p = p[:10] + randstring(28) + p[10 + 28:] + # Change randstring to a CSPRNG + pkt = pkt[:10] + os.urandom(28) + pkt[10 + 28:] # if no ciphersuites were provided, we add a few usual, supported # ciphersuites along with the appropriate extensions if self.ciphers is None: cipherstart = 39 + (self.sidlen or 0) s = b"001ac02bc023c02fc027009e0067009c003cc009c0130033002f000a" - p = p[:cipherstart] + bytes_hex(s) + p[cipherstart + 2:] + #pkt = pkt[:cipherstart] + bytes_hex(s) + pkt[cipherstart + 2:] + pkt = pkt[:cipherstart] + hex_bytes(s) + pkt[cipherstart + 2:] if self.ext is None: ext_len = b'\x00\x2c' ext_reneg = b'\xff\x01\x00\x01\x00' ext_sn = b'\x00\x00\x00\x0f\x00\r\x00\x00\nsecdev.org' ext_sigalg = b'\x00\r\x00\x08\x00\x06\x04\x03\x04\x01\x02\x01' ext_supgroups = b'\x00\n\x00\x04\x00\x02\x00\x17' - p += ext_len + ext_reneg + ext_sn + ext_sigalg + ext_supgroups + pkt += ext_len + ext_reneg + ext_sn + ext_sigalg + ext_supgroups - return super(TLSClientHello, self).post_build(p, pay) + return super(TLSClientHello, self).post_build(pkt, pay) def tls_session_update(self, msg_str): """ @@ -284,25 +298,202 @@ def tls_session_update(self, msg_str): """ super(TLSClientHello, self).tls_session_update(msg_str) - self.tls_session.advertised_tls_version = self.version + s = self.tls_session self.random_bytes = msg_str[10:38] - self.tls_session.client_random = (struct.pack('!I', - self.gmt_unix_time) + - self.random_bytes) + s.client_random = (struct.pack('!I', self.gmt_unix_time) + self.random_bytes) + if self.sidlen and self.sidlen > 0: + self.tls_session.sid = self.sid + + is_tls13 = False if self.ext: for e in self.ext: - if isinstance(e, TLS_Ext_SupportedVersions): - if self.tls_session.tls13_early_secret is None: - # this is not recomputed if there was a TLS 1.3 HRR - self.tls_session.compute_tls13_early_secrets() - break + + if isinstance(e, TLS_Ext_SupportedVersion_CH): + is_tls13 = True + s.advertised_tls_version = e.versions[0] + # No distinction between a TLS 1.2 ClientHello and a TLS + # 1.3 ClientHello when dissecting : TLS 1.3 CH will be + # parsed as TLSClientHello + + if isinstance(e, TLS_Ext_SignatureAlgorithms): + s.advertised_sig_algs = e.sig_algs + + if isinstance(e, TLS_Ext_EarlyDataIndication): + # XXX external case + connection_end = s.connection_end + from scapy.layers.tls.crypto.suites import _tls_cipher_suites_cls + cs_cls = _tls_cipher_suites_cls[s.tls13_ticket_ciphersuite] + + if connection_end == "server": + s.prcs = readConnState(ciphersuite=cs_cls, + connection_end=connection_end, + tls_version=0x0304) + s.triggered_prcs_commit = True + elif connection_end == "client": + s.pwcs = writeConnState(ciphersuite=cs_cls, + connection_end=connection_end, + tls_version=0x0304) + s.triggered_pwcs_commit = True + + s.compute_tls13_early_secrets() + + if not is_tls13: + self.tls_session.advertised_tls_version = self.version + + +class TLS13ClientHello(_TLSHandshake): + """ + TLS ClientHello, with abilities to handle extensions. + + The Random structure follows the RFC 5246: while it is 32-byte long, + many implementations use the first 4 bytes as a gmt_unix_time, and then + the remaining 28 byts should be completely random. This was designed in + order to (sort of) mitigate broken RNGs. If you prefer to show the full + 32 random bytes without any GMT time, just comment in/out the lines below. + """ + name = "TLS 1.3 Handshake - Client Hello" + fields_desc = [ByteEnumField("msgtype", 1, _tls_handshake_type), + ThreeBytesField("msglen", None), + _TLSClientVersionField("version", None, _tls_version), + + _TLSRandomBytesField("random_bytes", None, 32), + + FieldLenField("sidlen", None, fmt="B", length_of="sid"), + _SessionIDField("sid", "", + length_from=lambda pkt: pkt.sidlen), + + FieldLenField("cipherslen", None, fmt="!H", + length_of="ciphers"), + _CipherSuitesField("ciphers", None, + _tls_cipher_suites, itemfmt="!H", + length_from=lambda pkt: pkt.cipherslen), + + FieldLenField("complen", None, fmt="B", length_of="comp"), + _CompressionMethodsField("comp", [0], + _tls_compression_algs, + itemfmt="B", + length_from=lambda pkt: pkt.complen), # noqa: E501 + + _ExtensionsLenField("extlen", None, length_of="ext"), + _ExtensionsField("ext", None, + length_from=lambda pkt: (pkt.msglen - + (pkt.sidlen or 0) - # noqa: E501 + (pkt.cipherslen or 0) - # noqa: E501 + (pkt.complen or 0) - # noqa: E501 + 40))] + + def post_build(self, pkt, pay): + if self.random_bytes is None: + pkt = pkt[:6] + os.urandom(32) + pkt[6 + 32:] + # We don't call the post_build function from class _TLSHandshake + # to compute the message length because we need that value now + # for the HMAC in binder + tmp_len = len(pkt) + if self.msglen is None: + sz = tmp_len - 4 + pkt = struct.pack("!I", (orb(pkt[0]) << 24) | sz) + pkt[4:] + + if self.ext: + for e in self.ext: + if isinstance(e, TLS_Ext_PreSharedKey_CH): + if s.client_session_ticket: + res_suite = s.tls13_ticket_ciphersuite + cs_cls = _tls_cipher_suites_cls[res_suite] + hkdf = TLS13_HKDF(cs_cls.hash_alg.name.lower()) + hash_len = hkdf.hash.digest_size + s.compute_tls13_early_secrets(external=False) + else: + hkdf = TLS13_HKDF("sha256") + hash_len = hkdf.hash.digest_size + s.compute_tls13_early_secrets(external=True) + + # RFC8446 4.2.11.2 + # "Each entry in the binders list is computed as an HMAC + # over a transcript hash (see Section 4.4.1) containing a + # partial ClientHello up to and including the + # PreSharedKeyExtension.identities field." + # PSK Binders field is : + # - PSK Binders length (2 bytes) + # - First PSK Binder length (1 byte) + + # HMAC (hash_len bytes) + # The PSK Binder is computed in the same way as the + # Finished message with binder_key as BaseKey + + if s.tls13_retry: + handshake_context = b"" + for m in s.handshake_messages: + handshake_context += m + + handshake_context = pkt[:-hash_len - 3] + + binder_key = s.tls13_derived_secrets["binder_key"] + psk_binder = hkdf.compute_verify_data(binder_key, + handshake_context) + + # Here, we replaced the last 32 bytes of the packet by the + # new HMAC values computed over the ClientHello (without + # the binders) + pkt = pkt[:-hash_len] + psk_binder + + return pkt + pay + + def tls_session_update(self, msg_str): + """ + Either for parsing or building, we store the client_random + along with the raw string representing this handshake message. + """ + super(TLS13ClientHello, self).tls_session_update(msg_str) + + s = self.tls_session + + if self.sidlen and self.sidlen > 0: + s.sid = self.sid + self.random_bytes = msg_str[10:38] + s.client_random = self.random_bytes + if self.ext: + for e in self.ext: + if isinstance(e, TLS_Ext_SupportedVersion_CH): + self.tls_session.advertised_tls_version = e.versions[0] + + # If the early_data extension is present, we need to compute + # the key material for the 0-RTT data + if isinstance(e, TLS_Ext_EarlyDataIndication): + # We need all the ClientHello to compute + # client_early_traffic, so we couldn't compute it in the + # post_build method. + s.compute_tls13_early_secrets() + + # XXX get resumed ciphersuite from argument + cs_val = 0x1301 + if cs_val not in _tls_cipher_suites_cls: + warning("Unknown cipher suite %d from ServerHello" % cs_val) # noqa: E501 + # we do not try to set a default nor stop the execution + else: + cs_cls = _tls_cipher_suites_cls[cs_val] + + connection_end = s.connection_end + + s.pwcs = writeConnState(ciphersuite=cs_cls, + connection_end=connection_end, + tls_version=0x0304) + + s.triggered_pwcs_commit = True + cets = s.tls13_derived_secrets["client_early_traffic_secret"] # noqa: E501 + s.pwcs.tls13_derive_keys(cets) + + if isinstance(e, TLS_Ext_SignatureAlgorithms): + s.advertised_sig_algs = e.sig_algs + + + + ############################################################################### # ServerHello # ############################################################################### -class TLSServerHello(TLSClientHello): +class TLSServerHello(_TLSHandshake): """ TLS ServerHello, with abilities to handle extensions. @@ -336,7 +527,6 @@ class TLSServerHello(TLSClientHello): length_from=lambda pkt: (pkt.msglen - (pkt.sidlen or 0) - # noqa: E501 38))] - # 40)) ] @classmethod def dispatch_hook(cls, _pkt=None, *args, **kargs): @@ -346,11 +536,12 @@ def dispatch_hook(cls, _pkt=None, *args, **kargs): return TLS13ServerHello return TLSServerHello - def post_build(self, p, pay): + def post_build(self, pkt, pay): if self.random_bytes is None: - p = p[:10] + randstring(28) + p[10 + 28:] - return super(TLSClientHello, self).post_build(p, pay) + pkt = pkt[:10] + os.urandom(28) + pkt[10 + 28:] + return super(TLSServerHello, self).post_build(pkt, pay) + # XXX add post_dissection method to check binder when pre_shared_key extension is present def tls_session_update(self, msg_str): """ Either for parsing or building, we store the server_random @@ -361,14 +552,14 @@ def tls_session_update(self, msg_str): negotiation when we learn the session keys, and eventually they are committed once a ChangeCipherSpec has been sent/received. """ - super(TLSClientHello, self).tls_session_update(msg_str) - + super(TLSServerHello, self).tls_session_update(msg_str) self.tls_session.tls_version = self.version self.random_bytes = msg_str[10:38] self.tls_session.server_random = (struct.pack('!I', self.gmt_unix_time) + self.random_bytes) self.tls_session.sid = self.sid + self.tls_session.ciphersuite = self.cipher cs_cls = None if self.cipher: @@ -399,19 +590,49 @@ def tls_session_update(self, msg_str): tls_version=self.version) -class TLS13ServerHello(TLSClientHello): +class TLS13ServerHello(_TLSHandshake): """ TLS 1.3 ServerHello """ name = "TLS 1.3 Handshake - Server Hello" fields_desc = [ByteEnumField("msgtype", 2, _tls_handshake_type), ThreeBytesField("msglen", None), - _TLSVersionField("version", None, _tls_version), + _TLSVersionField("version", 0x0303, _tls_version), _TLSRandomBytesField("random_bytes", None, 32), + + FieldLenField("sidlen", None, length_of="sid", fmt="B"), + + _SessionIDField("sid", "", + length_from=lambda pkt: pkt.sidlen), EnumField("cipher", None, _tls_cipher_suites), + _CompressionMethodsField("comp", [0], + _tls_compression_algs, + itemfmt="B", + length_from=lambda pkt: 1), _ExtensionsLenField("extlen", None, length_of="ext"), _ExtensionsField("ext", None, length_from=lambda pkt: (pkt.msglen - 38))] + # ServerHello and HelloRetryRequest has the same structure and the same + # msgId. We need to check the server_random value to determine which it is. + @classmethod + def dispatch_hook(cls, _pkt=None, *args, **kargs): + if _pkt and len(_pkt) >= 38: + # If SHA-256("HelloRetryRequest") == server_random, + # this message is a HelloRetryRequest + random_bytes = _pkt[6:38] + digest = hashes.Hash(hashes.SHA256(), backend=default_backend()) + digest.update(b"HelloRetryRequest") + digest_out = digest.finalize() + if random_bytes == digest_out: + return TLS13HelloRetryRequest + + return TLS13ServerHello + + def post_build(self, pkt, pay): + if self.random_bytes is None: + pkt = pkt[:6] + os.urandom(32) + pkt[6 + 32:] + return super(TLS13ServerHello, self).post_build(pkt, pay) + def tls_session_update(self, msg_str): """ Either for parsing or building, we store the server_random along with @@ -419,11 +640,16 @@ def tls_session_update(self, msg_str): cipher suite (if recognized), and finally we instantiate the write and read connection states. """ - super(TLSClientHello, self).tls_session_update(msg_str) + super(TLS13ServerHello, self).tls_session_update(msg_str) s = self.tls_session - s.tls_version = self.version + if self.ext: + for e in self.ext: + if isinstance(e, TLS_Ext_SupportedVersion_SH): + s.tls_version = e.version + break s.server_random = self.random_bytes + s.ciphersuite = self.cipher cs_cls = None if self.cipher: @@ -435,40 +661,109 @@ def tls_session_update(self, msg_str): cs_cls = _tls_cipher_suites_cls[cs_val] connection_end = s.connection_end - s.pwcs = writeConnState(ciphersuite=cs_cls, - connection_end=connection_end, - tls_version=self.version) - s.triggered_pwcs_commit = True - s.prcs = readConnState(ciphersuite=cs_cls, - connection_end=connection_end, - tls_version=self.version) - s.triggered_prcs_commit = True + +# s.pwcs = writeConnState(ciphersuite=cs_cls, +# connection_end=connection_end, +# tls_version=s.tls_version) +# s.triggered_pwcs_commit = True +# s.prcs = readConnState(ciphersuite=cs_cls, +# connection_end=connection_end, +# tls_version=s.tls_version) +# s.triggered_prcs_commit = True + + if connection_end == "server": + s.pwcs = writeConnState(ciphersuite=cs_cls, + connection_end=connection_end, + tls_version=s.tls_version) + s.triggered_pwcs_commit = True + elif connection_end == "client": + s.prcs = readConnState(ciphersuite=cs_cls, + connection_end=connection_end, + tls_version=s.tls_version) + s.triggered_prcs_commit = True if self.tls_session.tls13_early_secret is None: # In case the connState was not pre-initialized, we could not # compute the early secrets at the ClientHello, so we do it here. self.tls_session.compute_tls13_early_secrets() - s.compute_tls13_handshake_secrets() + s.compute_tls13_handshake_secrets() + if connection_end == "server": + shts = s.tls13_derived_secrets["server_handshake_traffic_secret"] + #chts = s.tls13_derived_secrets["client_handshake_traffic_secret"] + s.pwcs.tls13_derive_keys(shts) + #s.prcs.tls13_derive_keys(chts) + elif connection_end == "client": + shts = s.tls13_derived_secrets["server_handshake_traffic_secret"] + s.prcs.tls13_derive_keys(shts) + #chts = s.tls13_derived_secrets["client_handshake_traffic_secret"] + #s.pwcs.tls13_derive_keys(chts) ############################################################################### # HelloRetryRequest # ############################################################################### -class TLSHelloRetryRequest(_TLSHandshake): +class TLS13HelloRetryRequest(_TLSHandshake): name = "TLS 1.3 Handshake - Hello Retry Request" - fields_desc = [ByteEnumField("msgtype", 6, _tls_handshake_type), + + fields_desc = [ByteEnumField("msgtype", 2, _tls_handshake_type), ThreeBytesField("msglen", None), - _TLSVersionField("version", None, _tls_version), + _TLSVersionField("version", 0x0303, _tls_version), + _TLSRandomBytesField("random_bytes", None, 32), + FieldLenField("sidlen", None, length_of="sid", fmt="B"), + _SessionIDField("sid", "", + length_from=lambda pkt: pkt.sidlen), + EnumField("cipher", None, _tls_cipher_suites), + _CompressionMethodsField("comp", [0], + _tls_compression_algs, + itemfmt="B", + length_from=lambda pkt: 1), _ExtensionsLenField("extlen", None, length_of="ext"), _ExtensionsField("ext", None, - length_from=lambda pkt: pkt.msglen - 4)] + length_from=lambda pkt: (pkt.msglen - + 38))] + def build(self): + fval = self.getfieldval("random_bytes") + if fval is None: + digest = hashes.Hash(hashes.SHA256(), backend=default_backend()) + digest.update(b"HelloRetryRequest") + self.random_bytes = digest.finalize() + + return _TLSHandshake.build(self) + + def tls_session_update(self, msg_str): + s = self.tls_session + s.tls13_retry = True + s.tls13_client_pubshares = {} + + # If the server responds to a ClientHello with a HelloRetryRequest + # The value of the first ClientHello is replaced by a message_hash + if s.client_session_ticket: + cs_cls = _tls_cipher_suites_cls[s.tls13_ticket_ciphersuite] + hkdf = TLS13_HKDF(cs_cls.hash_alg.name.lower()) + hash_len = hkdf.hash.digest_size + else: + hkdf = TLS13_HKDF("sha256") + hash_len = hkdf.hash.digest_size + + handshake_context = struct.pack("B", 254) + handshake_context += struct.pack("B", 0) + handshake_context += struct.pack("B", 0) + handshake_context += struct.pack("B", hash_len) + digest = hashes.Hash(hkdf.hash, backend=default_backend()) + digest.update(s.handshake_messages[0]) + handshake_context += digest.finalize() + + s.handshake_messages[0] = handshake_context + + super(TLS13HelloRetryRequest, self).tls_session_update(msg_str) ############################################################################### # EncryptedExtensions # ############################################################################### + class TLSEncryptedExtensions(_TLSHandshake): name = "TLS 1.3 Handshake - Encrypted Extensions" fields_desc = [ByteEnumField("msgtype", 8, _tls_handshake_type), @@ -477,6 +772,67 @@ class TLSEncryptedExtensions(_TLSHandshake): _ExtensionsField("ext", None, length_from=lambda pkt: pkt.msglen - 2)] + def post_build_tls_session_update(self, msg_str): + self.tls_session_update(msg_str) + + s = self.tls_session + connection_end = s.connection_end + + # Check if the server early_data extension is present in + # EncryptedExtensions message (if so, early data was accepted by the + # server) + early_data_accepted = False + if self.ext: + for e in self.ext: + if isinstance(e, TLS_Ext_EarlyDataIndication): + early_data_accepted = True + + # If the serveur did not accept early_data, we change prcs traffic + # encryption keys. Otherwise, the the keys will be updated after the + # EndOfEarlyData message + if connection_end == "server": + if not early_data_accepted: + s.prcs = writeConnState(ciphersuite=type(s.wcs.ciphersuite), + connection_end=connection_end, + tls_version=s.tls_version) + + s.triggered_prcs_commit = True + chts = s.tls13_derived_secrets["client_handshake_traffic_secret"] # noqa: E501 + s.prcs.tls13_derive_keys(chts) + + s.rcs = self.tls_session.prcs + s.triggered_prcs_commit = False + + def post_dissection_tls_session_update(self, msg_str): + self.tls_session_update(msg_str) + s = self.tls_session + connection_end = s.connection_end + + # Check if the server early_data extension is present in + # EncryptedExtensions message (if so, early data was accepted by the + # server) + early_data_accepted = False + if self.ext: + for e in self.ext: + if isinstance(e, TLS_Ext_EarlyDataIndication): + early_data_accepted = True + + # If the serveur did not accept early_data, we change pwcs traffic + # encryption key. Otherwise, the the keys will be updated after the + # EndOfEarlyData message + if connection_end == "client": + if not early_data_accepted: + s.pwcs = writeConnState(ciphersuite=type(s.rcs.ciphersuite), + connection_end=connection_end, + tls_version=s.tls_version) + + s.triggered_pwcs_commit = True + chts = s.tls13_derived_secrets["client_handshake_traffic_secret"] # noqa: E501 + s.pwcs.tls13_derive_keys(chts) + + s.wcs = self.tls_session.pwcs + s.triggered_pwcs_commit = False + ############################################################################### # Certificate # @@ -620,7 +976,7 @@ def dispatch_hook(cls, _pkt=None, *args, **kargs): if _pkt: tls_session = kargs.get("tls_session", None) if tls_session and (tls_session.tls_version or 0) >= 0x0304: - return TLS13Certificate + return TLSCertificate return TLSCertificate def post_dissection_tls_session_update(self, msg_str): @@ -686,7 +1042,7 @@ class TLSServerKeyExchange(_TLSHandshake): _TLSSignatureField("sig", None, length_from=lambda pkt: pkt.msglen - len(pkt.params))] # noqa: E501 - def build(self, *args, **kargs): + def build(self): """ We overload build() method in order to provide a valid default value for params based on TLS session if not provided. This cannot be done by @@ -721,7 +1077,6 @@ def build(self, *args, **kargs): except Exception: if conf.debug_dissector: raise - pass else: cls = Raw() self.params = cls @@ -743,7 +1098,7 @@ def build(self, *args, **kargs): cls = Raw() self.sig = cls - return _TLSHandshake.build(self, *args, **kargs) + return _TLSHandshake.build(self) def post_dissection(self, pkt): """ @@ -755,16 +1110,15 @@ def post_dissection(self, pkt): s = self.tls_session if s.prcs and s.prcs.key_exchange.no_ske: pkt_info = pkt.firstlayer().summary() - log_runtime.info("TLS: useless ServerKeyExchange [%s]", pkt_info) if (s.prcs and - not s.prcs.key_exchange.anonymous and - s.client_random and s.server_random and + not s.prcs.key_exchange.anonymous and + s.client_random and s.server_random and s.server_certs and len(s.server_certs) > 0): m = s.client_random + s.server_random + raw(self.params) sig_test = self.sig._verify_sig(m, s.server_certs[0]) if not sig_test: pkt_info = pkt.firstlayer().summary() - log_runtime.info("TLS: invalid ServerKeyExchange signature [%s]", pkt_info) # noqa: E501 + log_runtime.info("TLS: invalid ServerKeyExchange signature [%s]", pkt_info) # noqa: E501 ############################################################################### @@ -818,8 +1172,8 @@ def addfield(self, pkt, s, val): def i2len(self, pkt, val): if val is None: return 0 - else: - return len(self.i2m(pkt, val)) + + return len(self.i2m(pkt, val)) class TLSCertificateRequest(_TLSHandshake): @@ -843,6 +1197,20 @@ class TLSCertificateRequest(_TLSHandshake): length_from=lambda pkt: pkt.certauthlen)] # noqa: E501 +class TLS13CertificateRequest(_TLSHandshake): + name = "TLS 1.3 Handshake - Certificate Request" + fields_desc = [ByteEnumField("msgtype", 13, _tls_handshake_type), + ThreeBytesField("msglen", None), + FieldLenField("cert_req_ctxt_len", None, fmt="B", + length_of="cert_req_ctxt"), + StrLenField("cert_req_ctxt", "", + length_from=lambda pkt: pkt.cert_req_ctxt_len), + _ExtensionsLenField("extlen", None, length_of="ext"), + _ExtensionsField("ext", None, + length_from=lambda pkt: pkt.msglen - + pkt.cert_req_ctxt_len - 3)] + + ############################################################################### # ServerHelloDone # ############################################################################### @@ -864,16 +1232,16 @@ class TLSCertificateVerify(_TLSHandshake): _TLSSignatureField("sig", None, length_from=lambda pkt: pkt.msglen)] - def build(self, *args, **kargs): + def build(self): sig = self.getfieldval("sig") if sig is None: s = self.tls_session m = b"".join(s.handshake_messages) if s.tls_version >= 0x0304: if s.connection_end == "client": - context_string = "TLS 1.3, client CertificateVerify" + context_string = b"TLS 1.3, client CertificateVerify" elif s.connection_end == "server": - context_string = "TLS 1.3, server CertificateVerify" + context_string = b"TLS 1.3, server CertificateVerify" m = b"\x20" * 64 + context_string + b"\x00" + s.wcs.hash.digest(m) # noqa: E501 self.sig = _TLSSignature(tls_session=s) if s.connection_end == "client": @@ -881,10 +1249,14 @@ def build(self, *args, **kargs): elif s.connection_end == "server": # should be TLS 1.3 only self.sig._update_sig(m, s.server_key) - return _TLSHandshake.build(self, *args, **kargs) + return _TLSHandshake.build(self) def post_dissection(self, pkt): s = self.tls_session + + if s.tls_version is None: + return + m = b"".join(s.handshake_messages) if s.tls_version >= 0x0304: if s.connection_end == "client": @@ -892,13 +1264,12 @@ def post_dissection(self, pkt): elif s.connection_end == "server": context_string = b"TLS 1.3, client CertificateVerify" m = b"\x20" * 64 + context_string + b"\x00" + s.rcs.hash.digest(m) - if s.connection_end == "server": if s.client_certs and len(s.client_certs) > 0: sig_test = self.sig._verify_sig(m, s.client_certs[0]) if not sig_test: pkt_info = pkt.firstlayer().summary() - log_runtime.info("TLS: invalid CertificateVerify signature [%s]", pkt_info) # noqa: E501 + log_runtime.info("TLS: invalid CertificateVerify signature [%s]", pkt_info) # noqa: E501 elif s.connection_end == "client": # should be TLS 1.3 only if s.server_certs and len(s.server_certs) > 0: @@ -907,7 +1278,6 @@ def post_dissection(self, pkt): pkt_info = pkt.firstlayer().summary() log_runtime.info("TLS: invalid CertificateVerify signature [%s]", pkt_info) # noqa: E501 - ############################################################################### # ClientKeyExchange # ############################################################################### @@ -952,7 +1322,7 @@ class TLSClientKeyExchange(_TLSHandshake): _TLSCKExchKeysField("exchkeys", length_from=lambda pkt: pkt.msglen)] - def build(self, *args, **kargs): + def build(self): fval = self.getfieldval("exchkeys") if fval is None: s = self.tls_session @@ -962,7 +1332,7 @@ def build(self, *args, **kargs): else: cls = Raw() self.exchkeys = cls - return _TLSHandshake.build(self, *args, **kargs) + return _TLSHandshake.build(self) ############################################################################### @@ -986,7 +1356,7 @@ class TLSFinished(_TLSHandshake): ThreeBytesField("msglen", None), _VerifyDataField("vdata", None)] - def build(self, *args, **kargs): + def build(self): fval = self.getfieldval("vdata") if fval is None: s = self.tls_session @@ -998,7 +1368,7 @@ def build(self, *args, **kargs): handshake_msg, ms) else: self.vdata = s.compute_tls13_verify_data(con_end, "write") - return _TLSHandshake.build(self, *args, **kargs) + return _TLSHandshake.build(self) def post_dissection(self, pkt): s = self.tls_session @@ -1011,13 +1381,13 @@ def post_dissection(self, pkt): handshake_msg, ms) if self.vdata != verify_data: pkt_info = pkt.firstlayer().summary() - log_runtime.info("TLS: invalid Finished received [%s]", pkt_info) # noqa: E501 + log_runtime.info("TLS: invalid Finished received [%s]", pkt_info) # noqa: E501 elif s.tls_version >= 0x0304: con_end = s.connection_end verify_data = s.compute_tls13_verify_data(con_end, "read") if self.vdata != verify_data: pkt_info = pkt.firstlayer().summary() - log_runtime.info("TLS: invalid Finished received [%s]", pkt_info) # noqa: E501 + log_runtime.info("TLS: invalid Finished received [%s]", pkt_info) # noqa: E501 def post_build_tls_session_update(self, msg_str): self.tls_session_update(msg_str) @@ -1086,7 +1456,7 @@ class URLAndOptionalHash(Packet): StrLenField("hash", "", length_from=lambda pkt: 20 * pkt.hash_present)] - def guess_payload_class(self, p): + def guess_payload_class(self, payload): return Padding @@ -1155,9 +1525,9 @@ class SupDataEntry(Packet): fields_desc = [ShortField("sdtype", None), FieldLenField("len", None, length_of="data"), StrLenField("data", "", - length_from=lambda pkt:pkt.len)] + length_from=lambda pkt: pkt.len)] - def guess_payload_class(self, p): + def guess_payload_class(self, payload): return Padding @@ -1168,7 +1538,7 @@ class UserMappingData(Packet): StrLenField("data", "", length_from=lambda pkt: pkt.len)] - def guess_payload_class(self, p): + def guess_payload_class(self, payload): return Padding @@ -1179,9 +1549,9 @@ class SupDataEntryUM(Packet): adjust=lambda pkt, x: x + 2), FieldLenField("dlen", None, length_of="data"), PacketListField("data", [], UserMappingData, - length_from=lambda pkt:pkt.dlen)] + length_from=lambda pkt: pkt.dlen)] - def guess_payload_class(self, p): + def guess_payload_class(self, payload): return Padding @@ -1232,6 +1602,10 @@ class TLS13NewSessionTicket(_TLSHandshake): ThreeBytesField("msglen", None), IntField("ticket_lifetime", 0xffffffff), IntField("ticket_age_add", 0), + FieldLenField("noncelen", None, fmt="B", + length_of="ticket_nonce"), + StrLenField("ticket_nonce", "", + length_from=lambda pkt: pkt.noncelen), FieldLenField("ticketlen", None, length_of="ticket"), # TicketField("ticket", "", StrLenField("ticket", "", @@ -1240,7 +1614,34 @@ class TLS13NewSessionTicket(_TLSHandshake): _ExtensionsField("ext", None, length_from=lambda pkt: (pkt.msglen - (pkt.ticketlen or 0) - # noqa: E501 - 12))] + pkt.noncelen or 0) - 13)] # noqa: E501 + + + def build(self): + fval = self.getfieldval("ticket") + if fval == b"": + # For testing purposes, we set the ticket + # to a random label... + # The ticket may also be a self-encrypted and self-authenticated + # value + self.ticket = os.urandom(48) + + fval = self.getfieldval("ticket_nonce") + if fval == b"": + # We choose a random Nonce... + self.ticket_nonce = os.urandom(32) + + fval = self.getfieldval("ticket_lifetime") + if fval == 0xffffffff: + # We fixe the ticket_lifetime to 12 hours + self.ticket_lifetime = 43200 + + fval = self.getfieldval("ticket_age_add") + if fval == 0: + # ticket_age_add is a random 32-bit value + self.ticket_age_add = struct.unpack("!I", os.urandom(4))[0] + + return _TLSHandshake.build(self) def post_dissection_tls_session_update(self, msg_str): self.tls_session_update(msg_str) @@ -1248,16 +1649,91 @@ def post_dissection_tls_session_update(self, msg_str): self.tls_session.client_session_ticket = self.ticket +class TLS13EndOfEarlyData(_TLSHandshake): + name = "TLS 1.3 Handshake - End Of Early Data" + + fields_desc = [ByteEnumField("msgtype", 5, _tls_handshake_type), + ThreeBytesField("msglen", None)] + + def post_build_tls_session_update(self, msg_str): + self.tls_session_update(msg_str) + s = self.tls_session + connection_end = s.connection_end + if connection_end == "client": + + s.pwcs = writeConnState(ciphersuite=type(s.rcs.ciphersuite), + connection_end=connection_end, + tls_version=s.tls_version) + + s.triggered_pwcs_commit = True + chts = s.tls13_derived_secrets["client_handshake_traffic_secret"] + s.pwcs.tls13_derive_keys(chts) + + def post_dissection_tls_session_update(self, msg_str): + self.tls_session_update(msg_str) + s = self.tls_session + connection_end = s.connection_end + if connection_end == "server": + s.prcs = writeConnState(ciphersuite=type(s.rcs.ciphersuite), + connection_end=connection_end, + tls_version=s.tls_version) + + s.triggered_prcs_commit = True + chts = s.tls13_derived_secrets["client_handshake_traffic_secret"] + s.prcs.tls13_derive_keys(chts) + + +_key_update_request = {0: "update_not_requested", 1: "update_requested"} + + +class TLS13KeyUpdate(_TLSHandshake): + name = "TLS 1.3 Handshake - Key Update" + fields_desc = [ByteEnumField("msgtype", 24, _tls_handshake_type), + ThreeBytesField("msglen", None), + ByteEnumField("request_update", 0, _key_update_request)] + + def post_build_tls_session_update(self, msg_str): + s = self.tls_session + s.pwcs = writeConnState(ciphersuite=type(s.wcs.ciphersuite), + connection_end=s.connection_end, + tls_version=s.tls_version) + s.triggered_pwcs_commit = True + s.compute_tls13_next_traffic_secrets(s.connection_end, "write") + + def post_dissection_tls_session_update(self, msg_str): + s = self.tls_session + s.prcs = writeConnState(ciphersuite=type(s.rcs.ciphersuite), + connection_end=s.connection_end, + tls_version=s.tls_version) + s.triggered_prcs_commit = True + if s.connection_end == "server": + s.compute_tls13_next_traffic_secrets("client", "read") + elif s.connection_end == "client": + s.compute_tls13_next_traffic_secrets("server", "read") + + +class TLS13MessageHash(_TLSHandshake): + pass + + ############################################################################### # All handshake messages defined in this module # ############################################################################### _tls_handshake_cls = {0: TLSHelloRequest, 1: TLSClientHello, 2: TLSServerHello, 3: TLSHelloVerifyRequest, - 4: TLSNewSessionTicket, 6: TLSHelloRetryRequest, + 4: TLSNewSessionTicket, 8: TLSEncryptedExtensions, 11: TLSCertificate, 12: TLSServerKeyExchange, 13: TLSCertificateRequest, 14: TLSServerHelloDone, 15: TLSCertificateVerify, 16: TLSClientKeyExchange, 20: TLSFinished, 21: TLSCertificateURL, 22: TLSCertificateStatus, 23: TLSSupplementalData} + + +_tls13_handshake_cls = {1: TLS13ClientHello, 2: TLS13ServerHello, + 4: TLS13NewSessionTicket, 5: TLS13EndOfEarlyData, + 8: TLSEncryptedExtensions, 11: TLS13Certificate, + 13: TLS13CertificateRequest, 15: TLSCertificateVerify, + 20: TLSFinished, 24: TLS13KeyUpdate, + 254: TLS13MessageHash} diff --git a/scapy/layers/tls/keyexchange.py b/scapy/layers/tls/keyexchange.py index 836008e44cc..9d92044d04c 100644 --- a/scapy/layers/tls/keyexchange.py +++ b/scapy/layers/tls/keyexchange.py @@ -49,11 +49,12 @@ 0x0502: "sha384+dsa", 0x0503: "sha384+ecdsa", 0x0600: "sha512+anon", 0x0601: "sha512+rsa", 0x0602: "sha512+dsa", 0x0603: "sha512+ecdsa", - 0x0804: "sha256+rsapss", - 0x0805: "sha384+rsapss", - 0x0806: "sha512+rsapss", + 0x0804: "sha256+rsaepss", + 0x0805: "sha384+rsaepss", + 0x0806: "sha512+rsaepss", 0x0807: "ed25519", - 0x0808: "ed448"} + 0x0808: "ed448", + 0x0809: "sha256+rsapss"} def phantom_mode(pkt): @@ -155,7 +156,7 @@ class _TLSSignature(_GenericTLSSessionInheritance): #XXX 'sig_alg' should be set in __init__ depending on the context. """ name = "TLS Digital Signature" - fields_desc = [SigAndHashAlgField("sig_alg", 0x0401, _tls_hash_sig), + fields_desc = [SigAndHashAlgField("sig_alg", 0x0804, _tls_hash_sig), SigLenField("sig_len", None, fmt="!H", length_of="sig_val"), SigValField("sig_val", None, @@ -164,7 +165,7 @@ class _TLSSignature(_GenericTLSSessionInheritance): def __init__(self, *args, **kargs): super(_TLSSignature, self).__init__(*args, **kargs) if (self.tls_session and - self.tls_session.tls_version and + self.tls_session.tls_version and self.tls_session.tls_version < 0x0303): self.sig_alg = None @@ -183,6 +184,8 @@ def _update_sig(self, m, key): h, sig = _tls_hash_sig[self.sig_alg].split('+') if sig.endswith('pss'): t = "pss" + elif sig.endswith('ecdsa'): + t = "" else: t = "pkcs" self.sig_val = key.sign(m, t=t, h=h) @@ -195,7 +198,9 @@ def _verify_sig(self, m, cert): if self.sig_val: if self.sig_alg: h, sig = _tls_hash_sig[self.sig_alg].split('+') - if sig.endswith('pss'): + if sig.endswith('ecdsa'): + return cert.verify(m, self.sig_val, t="") + elif sig.endswith('pss'): t = "pss" else: t = "pkcs" @@ -207,7 +212,7 @@ def _verify_sig(self, m, cert): return cert.verify(m, self.sig_val, t='pkcs', h='md5') return False - def guess_payload_class(self, p): + def guess_payload_class(self, payload): return Padding @@ -235,7 +240,7 @@ def getfield(self, pkt, s): remain = b"" if conf.padding_layer in i: r = i[conf.padding_layer] - del(r.underlayer.payload) + del r.underlayer.payload remain = r.load return remain, i @@ -365,13 +370,13 @@ def register_pubkey(self): if not s.client_kx_ffdh_params: s.client_kx_ffdh_params = pn.parameters(default_backend()) - def post_dissection(self, r): + def post_dissection(self, pkt): try: self.register_pubkey() except ImportError: pass - def guess_payload_class(self, p): + def guess_payload_class(self, payload): """ The signature after the params gets saved as Padding. This way, the .getfield() which _TLSServerParamsField inherits @@ -405,7 +410,7 @@ class ECTrinomialBasis(Packet): fields_desc = [FieldLenField("klen", None, length_of="k", fmt="B"), StrLenField("k", "", length_from=lambda pkt: pkt.klen)] - def guess_payload_class(self, p): + def guess_payload_class(self, payload): return Padding @@ -419,7 +424,7 @@ class ECPentanomialBasis(Packet): FieldLenField("k3len", None, length_of="k3", fmt="B"), StrLenField("k3", "", length_from=lambda pkt: pkt.k3len)] - def guess_payload_class(self, p): + def guess_payload_class(self, payload): return Padding @@ -504,7 +509,7 @@ def fill_missing(self): if self.curve_type is None: self.curve_type = _tls_ec_curve_types["explicit_prime"] - def guess_payload_class(self, p): + def guess_payload_class(self, payload): return Padding @@ -622,13 +627,13 @@ def register_pubkey(self): if not s.client_kx_ecdh_params: s.client_kx_ecdh_params = curve - def post_dissection(self, r): + def post_dissection(self, pkt): try: self.register_pubkey() except ImportError: pass - def guess_payload_class(self, p): + def guess_payload_class(self, payload): return Padding @@ -693,7 +698,7 @@ def post_dissection(self, pkt): except ImportError: pass - def guess_payload_class(self, p): + def guess_payload_class(self, payload): return Padding @@ -718,7 +723,7 @@ def fill_missing(self): def post_dissection(self, pkt): pass - def guess_payload_class(self, p): + def guess_payload_class(self, payload): return Padding @@ -786,7 +791,7 @@ def post_dissection(self, m): s.pre_master_secret = ZZ s.compute_ms_and_derive_keys() - def guess_payload_class(self, p): + def guess_payload_class(self, payload): return Padding @@ -929,7 +934,7 @@ def post_build(self, pkt, pay): tmp_len = struct.pack("!H", len(enc)) return tmp_len + enc + pay - def guess_payload_class(self, p): + def guess_payload_class(self, payload): return Padding diff --git a/scapy/layers/tls/keyexchange_tls13.py b/scapy/layers/tls/keyexchange_tls13.py index 951dbeb0090..23adeae4ade 100644 --- a/scapy/layers/tls/keyexchange_tls13.py +++ b/scapy/layers/tls/keyexchange_tls13.py @@ -18,6 +18,7 @@ from scapy.layers.tls.crypto.groups import _tls_named_ffdh_groups, \ _tls_named_curves, _ffdh_groups, \ _tls_named_groups + import scapy.modules.six as six if conf.crypto_valid: @@ -119,11 +120,12 @@ def register_pubkey(self): self.pubkey = import_point(self.key_exchange) elif _tls_named_curves[self.group] != "x448": curve = ec._CURVE_TYPES[_tls_named_curves[self.group]]() - import_point = ec.EllipticCurvePublicNumbers.from_encoded_point - public_numbers = import_point(curve, self.key_exchange) + #import_point = ec.EllipticCurvePublicNumbers.from_encoded_point + import_point = ec.EllipticCurvePublicKey.from_encoded_point + public_numbers = import_point(curve, self.key_exchange).public_numbers() self.pubkey = public_numbers.public_key(default_backend()) - def post_dissection(self, r): + def post_dissection(self, pkt): try: self.register_pubkey() except ImportError: @@ -135,7 +137,7 @@ def extract_padding(self, s): class TLS_Ext_KeyShare_CH(TLS_Ext_Unknown): name = "TLS Extension - Key Share (for ClientHello)" - fields_desc = [ShortEnumField("type", 0x28, _tls_ext), + fields_desc = [ShortEnumField("type", 0x33, _tls_ext), ShortField("len", None), FieldLenField("client_shares_len", None, length_of="client_shares"), @@ -149,34 +151,34 @@ def post_build(self, pkt, pay): if kse.privkey: if _tls_named_curves[kse.group] in privshares: pkt_info = pkt.firstlayer().summary() - log_runtime.info("TLS: group %s used twice in the same ClientHello [%s]", kse.group, pkt_info) # noqa: E501 + log_runtime.info("TLS: group %s used twice in the same ClientHello [%s]", kse.group, pkt_info) # noqa: E501 break privshares[_tls_named_groups[kse.group]] = kse.privkey return super(TLS_Ext_KeyShare_CH, self).post_build(pkt, pay) - def post_dissection(self, r): + def post_dissection(self, pkt): if not self.tls_session.frozen: for kse in self.client_shares: if kse.pubkey: pubshares = self.tls_session.tls13_client_pubshares if _tls_named_curves[kse.group] in pubshares: - pkt_info = r.firstlayer().summary() + pkt_info = pkt.firstlayer().summary() log_runtime.info("TLS: group %s used twice in the same ClientHello [%s]", kse.group, pkt_info) # noqa: E501 break pubshares[_tls_named_curves[kse.group]] = kse.pubkey - return super(TLS_Ext_KeyShare_CH, self).post_dissection(r) + return super(TLS_Ext_KeyShare_CH, self).post_dissection(pkt) class TLS_Ext_KeyShare_HRR(TLS_Ext_Unknown): name = "TLS Extension - Key Share (for HelloRetryRequest)" - fields_desc = [ShortEnumField("type", 0x28, _tls_ext), + fields_desc = [ShortEnumField("type", 0x33, _tls_ext), ShortField("len", None), ShortEnumField("selected_group", None, _tls_named_groups)] class TLS_Ext_KeyShare_SH(TLS_Ext_Unknown): name = "TLS Extension - Key Share (for ServerHello)" - fields_desc = [ShortEnumField("type", 0x28, _tls_ext), + fields_desc = [ShortEnumField("type", 0x33, _tls_ext), ShortField("len", None), PacketField("server_share", None, KeyShareEntry)] @@ -189,7 +191,6 @@ def post_build(self, pkt, pay): log_runtime.info("TLS: overwriting previous server key share [%s]", pkt_info) # noqa: E501 group_name = _tls_named_groups[self.server_share.group] privshare[group_name] = self.server_share.privkey - if group_name in self.tls_session.tls13_client_pubshares: privkey = self.server_share.privkey pubkey = self.tls_session.tls13_client_pubshares[group_name] @@ -203,13 +204,15 @@ def post_build(self, pkt, pay): self.tls_session.tls13_dhe_secret = pms return super(TLS_Ext_KeyShare_SH, self).post_build(pkt, pay) - def post_dissection(self, r): + def post_dissection(self, pkt): if not self.tls_session.frozen and self.server_share.pubkey: # if there is a pubkey, we assume the crypto library is ok pubshare = self.tls_session.tls13_server_pubshare - if len(pubshare) > 0: - pkt_info = r.firstlayer().summary() - log_runtime.info("TLS: overwriting previous server key share [%s]", pkt_info) # noqa: E501 + # if len(pubshare) > 0: + if pubshare: + pkt_info = pkt.firstlayer().summary() + log_runtime.info("TLS: overwriting previous server key share [%s]", pkt_info) # noqa: E501 + group_name = _tls_named_groups[self.server_share.group] pubshare[group_name] = self.server_share.pubkey @@ -224,12 +227,26 @@ def post_dissection(self, r): else: pms = privkey.exchange(ec.ECDH(), pubkey) self.tls_session.tls13_dhe_secret = pms - return super(TLS_Ext_KeyShare_SH, self).post_dissection(r) + elif group_name in self.tls_session.tls13_server_privshare: + pubkey = self.tls_session.tls13_client_pubshares[group_name] + privkey = self.tls_session.tls13_server_privshare[group_name] + if group_name in six.itervalues(_tls_named_ffdh_groups): + pms = privkey.exchange(pubkey) + elif group_name in six.itervalues(_tls_named_curves): + if group_name == "x25519": + pms = privkey.exchange(pubkey) + else: + pms = privkey.exchange(ec.ECDH(), pubkey) + self.tls_session.tls13_dhe_secret = pms + + return super(TLS_Ext_KeyShare_SH, self).post_dissection(pkt) _tls_ext_keyshare_cls = {1: TLS_Ext_KeyShare_CH, - 2: TLS_Ext_KeyShare_SH, - 6: TLS_Ext_KeyShare_HRR} + 2: TLS_Ext_KeyShare_SH} + + +_tls_ext_keyshare_hrr_cls = {2: TLS_Ext_KeyShare_HRR} class Ticket(Packet): @@ -275,7 +292,7 @@ class PSKBinderEntry(Packet): class TLS_Ext_PreSharedKey_CH(TLS_Ext_Unknown): # XXX define post_build and post_dissection methods name = "TLS Extension - Pre Shared Key (for ClientHello)" - fields_desc = [ShortEnumField("type", 0x28, _tls_ext), + fields_desc = [ShortEnumField("type", 0x29, _tls_ext), ShortField("len", None), FieldLenField("identities_len", None, length_of="identities"), diff --git a/scapy/layers/tls/record.py b/scapy/layers/tls/record.py index 758c2e8f689..284231d62bd 100644 --- a/scapy/layers/tls/record.py +++ b/scapy/layers/tls/record.py @@ -23,7 +23,8 @@ from scapy.layers.inet import TCP from scapy.layers.tls.session import _GenericTLSSessionInheritance from scapy.layers.tls.handshake import (_tls_handshake_cls, _TLSHandshake, - TLS13ServerHello) + _tls13_handshake_cls, TLS13ServerHello, + TLS13ClientHello) from scapy.layers.tls.basefields import (_TLSVersionField, _tls_version, _TLSIVField, _TLSMACField, _TLSPadField, _TLSPadLenField, @@ -40,11 +41,11 @@ # Util -def _tls_version_check(version, min): - """Returns if version >= min, or False if version == None""" +def _tls_version_check(version, version_min): + """Returns if version >= version_min, or False if version == None""" if version is None: return False - return version >= min + return version >= version_min ############################################################################### # TLS Record Protocol # @@ -89,7 +90,13 @@ def m2i(self, pkt, m): if pkt.type == 22: if len(m) >= 1: msgtype = orb(m[0]) - cls = _tls_handshake_cls.get(msgtype, Raw) + + if ((pkt.tls_session.advertised_tls_version == 0x0304) or + (pkt.tls_session.tls_version and + pkt.tls_session.tls_version == 0x0304)): + cls = _tls13_handshake_cls.get(msgtype, Raw) + else: + cls = _tls_handshake_cls.get(msgtype, Raw) elif pkt.type == 20: cls = TLSChangeCipherSpec elif pkt.type == 21: @@ -133,30 +140,30 @@ def getfield(self, pkt, s): if (((pkt.tls_session.tls_version or 0x0303) > 0x0200) and hasattr(pkt, "type") and pkt.type == 23): return ret, [TLSApplicationData(data=b"")] - else: - return ret, [Raw(load=b"")] + + return ret, [Raw(load=b"")] if False in six.itervalues(pkt.tls_session.rcs.cipher.ready): return ret, _TLSEncryptedContent(remain) - else: - while remain: - raw_msg = remain - p = self.m2i(pkt, remain) - if Padding in p: - pad = p[Padding] - remain = pad.load - del(pad.underlayer.payload) - if len(remain) != 0: - raw_msg = raw_msg[:-len(remain)] - else: - remain = b"" + + while remain: + raw_msg = remain + p = self.m2i(pkt, remain) + if Padding in p: + pad = p[Padding] + remain = pad.load + del pad.underlayer.payload + if len(remain) != 0: + raw_msg = raw_msg[:-len(remain)] + else: + remain = b"" - if isinstance(p, _GenericTLSSessionInheritance): - if not p.tls_session.frozen: - p.post_dissection_tls_session_update(raw_msg) + if isinstance(p, _GenericTLSSessionInheritance): + if not p.tls_session.frozen: + p.post_dissection_tls_session_update(raw_msg) - lst.append(p) - return remain + ret, lst + lst.append(p) + return remain + ret, lst def i2m(self, pkt, p): """ @@ -174,6 +181,7 @@ def i2m(self, pkt, p): pkt.type = 22 elif isinstance(p, TLSApplicationData): pkt.type = 23 + p.tls_session = pkt.tls_session if not pkt.tls_session.frozen: cur = p.raw_stateful() @@ -193,12 +201,17 @@ def addfield(self, pkt, s, val): res = b"" for p in val: res += self.i2m(pkt, p) + + # Add TLS13ClientHello in case of HelloRetryRequest if (isinstance(pkt, _GenericTLSSessionInheritance) and - _tls_version_check(pkt.tls_session.tls_version, 0x0304) and - not isinstance(pkt, TLS13ServerHello)): + _tls_version_check(pkt.tls_session.tls_version, 0x0304) and + not isinstance(pkt.msg[0], TLS13ServerHello) and + not isinstance(pkt.msg[0], TLS13ClientHello)): return s + res + if not pkt.type: pkt.type = 0 + hdr = struct.pack("!B", pkt.type) + s[1:5] return hdr + res @@ -281,6 +294,9 @@ def dispatch_hook(cls, _pkt=None, *args, **kargs): as SSLv2 records but TLS ones instead, but hey, we can't be held responsible for low-minded extensibility choices. """ + if 'tls_session' in kargs: + s = kargs['tls_session'] + if _pkt and len(_pkt) >= 2: byte0 = orb(_pkt[0]) byte1 = orb(_pkt[1]) @@ -289,10 +305,12 @@ def dispatch_hook(cls, _pkt=None, *args, **kargs): return SSLv2 else: s = kargs.get("tls_session", None) + if s and _tls_version_check(s.tls_version, 0x0304): if s.rcs and not isinstance(s.rcs.cipher, Cipher_NULL): from scapy.layers.tls.record_tls13 import TLS13 return TLS13 + if _pkt and len(_pkt) < 5: # Layer detected as TLS but too small to be a real packet (len<5). # Those packets are usually customly implemented @@ -310,6 +328,7 @@ def _tls_auth_decrypt(self, hdr, s): failure. Also, if the integrity check fails, a warning will be issued, but we still return the sliced (unauthenticated) plaintext. """ + try: read_seq_num = struct.pack("!Q", self.tls_session.rcs.seq_num) self.tls_session.rcs.seq_num += 1 @@ -323,7 +342,7 @@ def _tls_auth_decrypt(self, hdr, s): return e.args except AEADTagError as e: pkt_info = self.firstlayer().summary() - log_runtime.info("TLS: record integrity check failed [%s]", pkt_info) # noqa: E501 + log_runtime.info("TLS: record integrity check failed [%s]", pkt_info) # noqa: E501 return e.args def _tls_decrypt(self, s): @@ -445,7 +464,7 @@ def pre_dissect(self, s): is_mac_ok = self._tls_hmac_verify(chdr, cfrag, mac) if not is_mac_ok: pkt_info = self.firstlayer().summary() - log_runtime.info("TLS: record integrity check failed [%s]", pkt_info) # noqa: E501 + log_runtime.info("TLS: record integrity check failed [%s]", pkt_info) # noqa: E501 elif cipher_type == 'stream': # Decrypt @@ -470,7 +489,6 @@ def pre_dissect(self, s): is_mac_ok = self._tls_hmac_verify(chdr, cfrag, mac) if not is_mac_ok: pkt_info = self.firstlayer().summary() - log_runtime.info("TLS: record integrity check failed [%s]", pkt_info) # noqa: E501 elif cipher_type == 'aead': # Authenticated encryption @@ -491,7 +509,6 @@ def pre_dissect(self, s): self.deciphered_len = None reconstructed_body = iv + frag + mac + pad - return hdr + reconstructed_body + r def post_dissect(self, s): @@ -501,16 +518,19 @@ def post_dissect(self, s): nothing if the prcs was not set, as this probably means that we're working out-of-context (and we need to keep the default rcs). """ - if self.tls_session.triggered_prcs_commit: - if self.tls_session.prcs is not None: - self.tls_session.rcs = self.tls_session.prcs - self.tls_session.prcs = None - self.tls_session.triggered_prcs_commit = False - if self.tls_session.triggered_pwcs_commit: - if self.tls_session.pwcs is not None: - self.tls_session.wcs = self.tls_session.pwcs - self.tls_session.pwcs = None - self.tls_session.triggered_pwcs_commit = False + + if (self.tls_session.tls_version and + self.tls_session.tls_version <= 0x0303): + if self.tls_session.triggered_prcs_commit: + if self.tls_session.prcs is not None: + self.tls_session.rcs = self.tls_session.prcs + self.tls_session.prcs = None + self.tls_session.triggered_prcs_commit = False + if self.tls_session.triggered_pwcs_commit: + if self.tls_session.pwcs is not None: + self.tls_session.wcs = self.tls_session.pwcs + self.tls_session.pwcs = None + self.tls_session.triggered_pwcs_commit = False return s def do_dissect_payload(self, s): @@ -519,6 +539,7 @@ def do_dissect_payload(self, s): Note that overloading .guess_payload_class() would not be enough, as the TLS session to be used would get lost. """ + if s: try: p = TLS(s, _internal=1, _underlayer=self, diff --git a/scapy/layers/tls/record_tls13.py b/scapy/layers/tls/record_tls13.py index 30e770e7d10..3c4036b7c7e 100644 --- a/scapy/layers/tls/record_tls13.py +++ b/scapy/layers/tls/record_tls13.py @@ -15,7 +15,7 @@ from scapy.config import conf from scapy.error import log_runtime, warning -from scapy.compat import raw +from scapy.compat import raw, orb from scapy.fields import ByteEnumField, PacketField, XStrField from scapy.layers.tls.session import _GenericTLSSessionInheritance from scapy.layers.tls.basefields import _TLSVersionField, _tls_version, \ @@ -24,12 +24,13 @@ from scapy.layers.tls.crypto.cipher_aead import AEADTagError from scapy.layers.tls.crypto.cipher_stream import Cipher_NULL from scapy.layers.tls.crypto.common import CipherError - +from scapy.layers.tls.crypto.pkcs1 import pkcs_i2osp ############################################################################### # TLS Record Protocol # ############################################################################### + class TLSInnerPlaintext(_GenericTLSSessionInheritance): name = "TLS Inner Plaintext" fields_desc = [_TLSMsgListField("msg", []), @@ -90,7 +91,7 @@ class TLS13(_GenericTLSSessionInheritance): __slots__ = ["deciphered_len"] name = "TLS 1.3" fields_desc = [ByteEnumField("type", 0x17, _tls_type), - _TLSVersionField("version", 0x0301, _tls_version), + _TLSVersionField("version", 0x0303, _tls_version), _TLSLengthField("len", None), _TLSInnerPlaintextField("inner", TLSInnerPlaintext()), _TLSMACField("auth_tag", None)] @@ -99,6 +100,7 @@ def __init__(self, *args, **kargs): self.deciphered_len = kargs.get("deciphered_len", None) super(TLS13, self).__init__(*args, **kargs) + # Parsing methods def _tls_auth_decrypt(self, s): @@ -112,23 +114,34 @@ def _tls_auth_decrypt(self, s): rcs = self.tls_session.rcs read_seq_num = struct.pack("!Q", rcs.seq_num) rcs.seq_num += 1 + add_data = (pkcs_i2osp(self.type, 1) + + pkcs_i2osp(self.version, 2) + + pkcs_i2osp(len(s), 2)) + try: - return rcs.cipher.auth_decrypt(b"", s, read_seq_num) + return rcs.cipher.auth_decrypt(add_data, s, read_seq_num) except CipherError as e: return e.args except AEADTagError as e: pkt_info = self.firstlayer().summary() - log_runtime.info("TLS: record integrity check failed [%s]", pkt_info) # noqa: E501 + log_runtime.info("TLS: record integrity check failed [%s]", pkt_info) # noqa: E501 return e.args def pre_dissect(self, s): """ Decrypt, verify and decompress the message. """ + if self.tls_session.triggered_prcs_commit: + if self.tls_session.prcs is not None: + self.tls_session.rcs = self.tls_session.prcs + self.tls_session.prcs = None + self.tls_session.triggered_prcs_commit = False + if len(s) < 5: raise Exception("Invalid record: header is too short.") - if isinstance(self.tls_session.rcs.cipher, Cipher_NULL): + self.type = orb(s[0]) + if isinstance(self.tls_session.rcs.cipher, Cipher_NULL) or orb(s[0]) == 20: self.deciphered_len = None return s else: @@ -149,6 +162,7 @@ def post_dissect(self, s): self.tls_session.rcs = self.tls_session.prcs self.tls_session.prcs = None self.tls_session.triggered_prcs_commit = False + return s def do_dissect_payload(self, s): @@ -173,10 +187,15 @@ def _tls_auth_encrypt(self, s): """ Return the TLSCiphertext.encrypted_record for AEAD ciphers. """ + log_runtime.warning("self.version %s", self.version) wcs = self.tls_session.wcs write_seq_num = struct.pack("!Q", wcs.seq_num) wcs.seq_num += 1 - return wcs.cipher.auth_encrypt(s, b"", write_seq_num) + add_data = (pkcs_i2osp(self.type, 1) + + pkcs_i2osp(self.version, 2) + + pkcs_i2osp(len(s) + wcs.cipher.tag_len, 2)) + + return wcs.cipher.auth_encrypt(s, add_data, write_seq_num) def post_build(self, pkt, pay): """ @@ -184,9 +203,9 @@ def post_build(self, pkt, pay): """ # Compute the length of TLSPlaintext fragment hdr, frag = pkt[:5], pkt[5:] - if not isinstance(self.tls_session.rcs.cipher, Cipher_NULL): - frag = self._tls_auth_encrypt(frag) + if not isinstance(self.tls_session.wcs.cipher, Cipher_NULL): + frag = self._tls_auth_encrypt(frag) if self.len is not None: # The user gave us a 'len', let's respect this ultimately hdr = hdr[:3] + struct.pack("!H", self.len) diff --git a/scapy/layers/tls/session.py b/scapy/layers/tls/session.py index e619aa72718..31ae96772c5 100644 --- a/scapy/layers/tls/session.py +++ b/scapy/layers/tls/session.py @@ -409,6 +409,8 @@ def __init__(self, # The default value corresponds to TLS 1.2 (and TLS 1.3, incidentally). self.advertised_tls_version = 0x0303 + self.advertised_sig_algs = None + # The agreed-upon TLS version found in the ServerHello. self.tls_version = None @@ -418,6 +420,9 @@ def __init__(self, self.pre_master_secret = None self.master_secret = None + # The agreed-upon ciphersuite found in the ServerHello + self.ciphersuite = None + # A session ticket received by the client. self.client_session_ticket = None @@ -431,11 +436,15 @@ def __init__(self, # These attributes should only be used with TLS 1.3 connections. self.tls13_psk_secret = None + self.tls13_psk_mode = None self.tls13_early_secret = None self.tls13_dhe_secret = None self.tls13_handshake_secret = None self.tls13_master_secret = None self.tls13_derived_secrets = {} + self.post_handshake_auth = False + self.tls13_ticket_ciphersuite = None + self.tls13_retry = False # Handshake messages needed for Finished computation/validation. # No record layer headers, no HelloRequests, no ChangeCipherSpecs. @@ -558,98 +567,151 @@ def compute_sslv2_km_and_derive_keys(self): # Secrets management for TLS 1.3 - def compute_tls13_early_secrets(self): + # Compute the Early Secret and the binder key + + def compute_tls13_early_secrets(self, external=False): """ Ciphers key and IV are updated accordingly for 0-RTT data. self.handshake_messages should be ClientHello only. """ - # we use the prcs rather than the pwcs in a totally arbitrary way - if self.prcs is None: - # too soon - return - hkdf = self.prcs.hkdf + # we use the prcs rather than the pwcs in a totally arbitrary way + # if self.prcs is None: + # # too soon + # return - self.tls13_early_secret = hkdf.extract(None, - self.tls13_psk_secret) + # if no hash algorithm is set, default to SHA-256 + if self.prcs and self.prcs.hkdf: + hkdf = self.prcs.hkdf + elif self.pwcs and self.pwcs.hkdf: + hkdf = self.pwcs.hkdf + else: + hkdf = TLS13_HKDF("sha256") - bk = hkdf.derive_secret(self.tls13_early_secret, - b"external psk binder key", - # "resumption psk binder key", - b"") - self.tls13_derived_secrets["binder_key"] = bk + if self.tls13_early_secret is None: + self.tls13_early_secret = hkdf.extract(None, + self.tls13_psk_secret) + + if "binder_key" not in self.tls13_derived_secrets: + if external: + bk = hkdf.derive_secret(self.tls13_early_secret, + b"ext binder", + b"") + else: + bk = hkdf.derive_secret(self.tls13_early_secret, + b"res binder", + b"") - if len(self.handshake_messages) > 1: - # these secrets are not defined in case of HRR - return + self.tls13_derived_secrets["binder_key"] = bk cets = hkdf.derive_secret(self.tls13_early_secret, - b"client early traffic secret", + b"c e traffic", b"".join(self.handshake_messages)) - self.tls13_derived_secrets["client_early_traffic_secret"] = cets + self.tls13_derived_secrets["client_early_traffic_secret"] = cets ees = hkdf.derive_secret(self.tls13_early_secret, - b"early exporter master secret", + b"e exp master", b"".join(self.handshake_messages)) self.tls13_derived_secrets["early_exporter_secret"] = ees if self.connection_end == "server": - self.prcs.tls13_derive_keys(cets) + if self.prcs: + self.prcs.tls13_derive_keys(cets) elif self.connection_end == "client": - self.pwcs.tls13_derive_keys(cets) + if self.pwcs: + self.pwcs.tls13_derive_keys(cets) + + + + def compute_tls13_other_early_secrets(self): + # if no hash algorithm is set, default to SHA-256 + if self.prcs and self.prcs.hkdf: + hkdf = self.prcs.hkdf + elif self.pwcs and self.pwcs.hkdf: + hkdf = self.pwcs.hkdf + else: + hkdf = TLS13_HKDF("sha256") + + cets = hkdf.derive_secret(self.tls13_early_secret, + b"c e traffic", + b"".join(self.handshake_messages)) + + self.tls13_derived_secrets["client_early_traffic_secret"] = cets + ees = hkdf.derive_secret(self.tls13_early_secret, + b"e exp master", + b"".join(self.handshake_messages)) + self.tls13_derived_secrets["early_exporter_secret"] = ees + + # if self.connection_end == "server": + # if self.prcs: + # self.prcs.tls13_derive_keys(cets) + # elif self.connection_end == "client": + # if self.pwcs: + # self.pwcs.tls13_derive_keys(cets) def compute_tls13_handshake_secrets(self): """ Ciphers key and IV are updated accordingly for Handshake data. self.handshake_messages should be ClientHello...ServerHello. """ - if self.tls13_early_secret is None: - warning("No early secret. This is abnormal.") + if self.prcs: + hkdf = self.prcs.hkdf + elif self.pwcs: + hkdf = self.pwcs.hkdf + else: + raise - hkdf = self.prcs.hkdf + if self.tls13_early_secret is None: + self.tls13_early_secret = hkdf.extract(None, + self.tls13_psk_secret) - self.tls13_handshake_secret = hkdf.extract(self.tls13_early_secret, - self.tls13_dhe_secret) + secret = hkdf.derive_secret(self.tls13_early_secret, b"derived", b"") + self.tls13_handshake_secret = hkdf.extract(secret, self.tls13_dhe_secret) # noqa: E501 chts = hkdf.derive_secret(self.tls13_handshake_secret, - b"client handshake traffic secret", + b"c hs traffic", b"".join(self.handshake_messages)) self.tls13_derived_secrets["client_handshake_traffic_secret"] = chts shts = hkdf.derive_secret(self.tls13_handshake_secret, - b"server handshake traffic secret", + b"s hs traffic", b"".join(self.handshake_messages)) self.tls13_derived_secrets["server_handshake_traffic_secret"] = shts - if self.connection_end == "server": - self.prcs.tls13_derive_keys(chts) - self.pwcs.tls13_derive_keys(shts) - elif self.connection_end == "client": - self.pwcs.tls13_derive_keys(chts) - self.prcs.tls13_derive_keys(shts) + # if self.connection_end == "server": + # self.prcs.tls13_derive_keys(chts) + # self.pwcs.tls13_derive_keys(shts) + # elif self.connection_end == "client": + # self.pwcs.tls13_derive_keys(chts) + # self.prcs.tls13_derive_keys(shts) def compute_tls13_traffic_secrets(self): """ Ciphers key and IV are updated accordingly for Application data. self.handshake_messages should be ClientHello...ServerFinished. """ - hkdf = self.prcs.hkdf + if self.prcs and self.prcs.hkdf: + hkdf = self.prcs.hkdf + elif self.pwcs and self.pwcs.hkdf: + hkdf = self.pwcs.hkdf - self.tls13_master_secret = hkdf.extract(self.tls13_handshake_secret, - None) + tmp = hkdf.derive_secret(self.tls13_handshake_secret, + b"derived", + b"") + self.tls13_master_secret = hkdf.extract(tmp, None) cts0 = hkdf.derive_secret(self.tls13_master_secret, - b"client application traffic secret", + b"c ap traffic", b"".join(self.handshake_messages)) self.tls13_derived_secrets["client_traffic_secrets"] = [cts0] sts0 = hkdf.derive_secret(self.tls13_master_secret, - b"server application traffic secret", + b"s ap traffic", b"".join(self.handshake_messages)) self.tls13_derived_secrets["server_traffic_secrets"] = [sts0] es = hkdf.derive_secret(self.tls13_master_secret, - b"exporter master secret", + b"exp master", b"".join(self.handshake_messages)) self.tls13_derived_secrets["exporter_secret"] = es @@ -698,39 +760,59 @@ def compute_tls13_resumption_secret(self): hkdf = self.prcs.hkdf elif self.connection_end == "client": hkdf = self.pwcs.hkdf + rs = hkdf.derive_secret(self.tls13_master_secret, - b"resumption master secret", + b"res master", b"".join(self.handshake_messages)) self.tls13_derived_secrets["resumption_secret"] = rs - def compute_tls13_next_traffic_secrets(self): + def compute_tls13_next_traffic_secrets(self, connection_end, read_or_write): # noqa : E501 """ Ciphers key and IV are updated accordingly. """ - hkdf = self.prcs.hkdf - hl = hkdf.hash.digest_size + if self.rcs.hkdf: + hkdf = self.rcs.hkdf + hl = hkdf.hash.digest_size + elif self.wcs.hkdf: + hkdf = self.wcs.hkdf + hl = hkdf.hash.digest_size - cts = self.tls13_derived_secrets["client_traffic_secrets"] - ctsN = cts[-1] - ctsN_1 = hkdf.expand_label(ctsN, "application traffic secret", "", hl) - cts.append(ctsN_1) + if read_or_write == "read": + if connection_end == "client": + cts = self.tls13_derived_secrets["client_traffic_secrets"] + ctsN = cts[-1] + ctsN_1 = hkdf.expand_label(ctsN, b"traffic upd", b"", hl) + cts.append(ctsN_1) + self.prcs.tls13_derive_keys(ctsN_1) + elif connection_end == "server": + sts = self.tls13_derived_secrets["server_traffic_secrets"] + stsN = sts[-1] + stsN_1 = hkdf.expand_label(stsN, b"traffic upd", b"", hl) + sts.append(stsN_1) - stsN_1 = hkdf.expand_label(ctsN, "application traffic secret", "", hl) - cts.append(stsN_1) + self.prcs.tls13_derive_keys(stsN_1) - if self.connection_end == "server": - self.prcs.tls13_derive_keys(ctsN_1) - self.pwcs.tls13_derive_keys(stsN_1) - elif self.connection_end == "client": - self.pwcs.tls13_derive_keys(ctsN_1) - self.prcs.tls13_derive_keys(stsN_1) + elif read_or_write == "write": + if connection_end == "client": + cts = self.tls13_derived_secrets["client_traffic_secrets"] + ctsN = cts[-1] + ctsN_1 = hkdf.expand_label(ctsN, b"traffic upd", b"", hl) + cts.append(ctsN_1) + self.pwcs.tls13_derive_keys(ctsN_1) + elif connection_end == "server": + sts = self.tls13_derived_secrets["server_traffic_secrets"] + stsN = sts[-1] + stsN_1 = hkdf.expand_label(stsN, b"traffic upd", b"", hl) + sts.append(stsN_1) + + self.pwcs.tls13_derive_keys(stsN_1) # Tests for record building/parsing def consider_read_padding(self): # Return True if padding is needed. Used by TLSPadField. return (self.rcs.cipher.type == "block" and - not (False in six.itervalues(self.rcs.cipher.ready))) + not False in six.itervalues(self.rcs.cipher.ready)) def consider_write_padding(self): # Return True if padding is needed. Used by TLSPadField. @@ -762,7 +844,7 @@ def eq(self, other): ok = True if (not ok and - self.dport == other.sport and self.sport == other.dport and + self.dport == other.sport and self.sport == other.dport and self.ipdst == other.ipsrc and self.ipsrc == other.ipdst): ok = True @@ -799,6 +881,7 @@ class _GenericTLSSessionInheritance(Packet): def __init__(self, _pkt="", post_transform=None, _internal=0, _underlayer=None, tls_session=None, **fields): + try: setme = self.tls_session is None except Exception: @@ -850,6 +933,7 @@ def clone_with(self, payload=None, **kargs): return pkt def raw_stateful(self): + log_runtime.info("_GenericTLSSessionInheritance - raw_stateful") return super(_GenericTLSSessionInheritance, self).__bytes__() def str_stateful(self): diff --git a/test/tls.uts b/test/tls.uts index 67b7fff85d5..30169a7efb7 100644 --- a/test/tls.uts +++ b/test/tls.uts @@ -505,7 +505,7 @@ _all_aes_cbc_tests() = Crypto - AES cipher in GCM mode, auth_encrypt() and auth_decrypt() checks #https://tools.ietf.org/html/draft-mcgrew-gcm-test-01 - +from scapy.layers.tls.crypto.pkcs1 import pkcs_os2ip class _aes128gcm_test_1: k= b"\x4c\x80\xcd\xef\xbb\x5d\x10\xda\x90\x6a\xc7\x3c\x36\x13\xa6\x34" n= b"\x22\x43\x3c\x64\x48\x55\xec\x7d\x3a\x23\x4b\xfd" @@ -774,6 +774,8 @@ p7_data = b"\x17\x03\x03\x01\xf6\x00\x00\x00\x00\x00\x00\x00\x01?\x04iy\x00\x04 = Reading TLS test session - TLS parsing (no encryption) does not throw any error # We will need to distinguish between connection ends. See next XXX below. +from scapy.layers.tls.record import TLS + t1 = TLS(p1_ch) t2 = TLS(p2_sh, tls_session=t1.tls_session.mirror()) t3 = TLS(p3_cert, tls_session=t2.tls_session) @@ -792,6 +794,7 @@ len(t1.msg) == 1 = Reading TLS test session - TLS Record __getitem__ +from scapy.layers.tls.handshake import TLSClientHello TLSClientHello in t1 = Reading TLS test session - ClientHello @@ -811,6 +814,13 @@ assert(ch.comp == [0]) = Reading TLS test session - ClientHello extensions +from scapy.layers.tls.extensions import (TLS_Ext_ServerName, +TLS_Ext_RenegotiationInfo, TLS_Ext_SupportedGroups, +TLS_Ext_SupportedPointFormat, TLS_Ext_SessionTicket, TLS_Ext_NPN, +TLS_Ext_ALPN, TLS_Ext_SignatureAlgorithms, TLS_Ext_CSR, +OCSPStatusRequest) + + assert(ch.extlen == 146) ext = ch.ext assert(len(ext) == 9) @@ -843,6 +853,8 @@ ext[8].sig_algs[-1] == 0x0202 = Reading TLS test session - ServerHello +from scapy.layers.tls.handshake import TLSServerHello + assert(TLSServerHello in t2) sh = t2.msg[0] assert(isinstance(sh, TLSServerHello)) @@ -854,6 +866,7 @@ sh.ext[-1].protocols[-1].protocol == b"http/1.1" = Reading TLS test session - Certificate +from scapy.layers.tls.cert import Cert cert = t3.msg[0] assert(cert.certslen == 2670) assert(len(cert.certs) == 2) @@ -871,6 +884,7 @@ isinstance(t4.payload.payload.payload, NoPayload) = Reading TLS test session - CertificateStatus +from scapy.layers.tls.handshake import TLSCertificateStatus assert(isinstance(cert_stat, TLSCertificateStatus)) assert(cert_stat.responselen == 471) cert_stat.response[0].responseStatus == 0 @@ -878,6 +892,8 @@ cert_stat.response[0].responseStatus == 0 = Reading TLS test session - ServerKeyExchange +from scapy.layers.tls.handshake import TLSServerKeyExchange +from scapy.layers.tls.keyexchange import ServerECDHNamedCurveParams assert(isinstance(ske, TLSServerKeyExchange)) p = ske.params assert(isinstance(p, ServerECDHNamedCurveParams)) @@ -888,10 +904,12 @@ ske.sig.sig_val[:4] == b'y\x8aQ\x11' and ske.sig.sig_val[-4:] == b'`15\xef' = Reading TLS test session - ServerHelloDone +from scapy.layers.tls.handshake import TLSServerHelloDone assert(isinstance(shd, TLSServerHelloDone)) shd.msglen == 0 = Reading TLS test session - Context checks after 1st RTT +from scapy.layers.tls.crypto.pkcs1 import pkcs_i2osp t = shd.tls_session assert(len(t.handshake_messages) == 6) assert(t.handshake_messages_parsed[-1] is shd) @@ -914,6 +932,8 @@ t5 = TLS(p5_cke_ccs_fin, tls_session=t4.tls_session.mirror()) = Reading TLS test session - ClientKeyExchange +from scapy.layers.tls.handshake import TLSClientKeyExchange +from scapy.layers.tls.keyexchange import ClientECDiffieHellmanPublic cke = t5.msg[0] ccs = t5.payload.msg[0] rec_fin = t5.payload.payload @@ -927,6 +947,7 @@ assert(k.ecdh_Yc[:4] == b'\x04\xd2\x07\xce' and k.ecdh_Yc[-4:] == b'\xdc\x86[\xe = Reading TLS test session - ChangeCipherSpec +from scapy.layers.tls.record import TLSChangeCipherSpec assert(isinstance(ccs, TLSChangeCipherSpec)) ccs.msgtype == 1 @@ -944,6 +965,7 @@ fin.load == b'\xd9\xcb,\x8cM\xfd\xbc9\xaa\x05\xf3\xd3\xf3Z\x8a-' = Reading TLS test session - Ticket, CCS & Finished +from scapy.layers.tls.handshake import TLSNewSessionTicket t6 = TLS(p6_tick_ccs_fin, tls_session=t5.tls_session.mirror()) tick = t6.msg[0] assert(isinstance(tick, TLSNewSessionTicket)) @@ -1019,6 +1041,7 @@ ccs = t6.payload.msg[0] assert isinstance(ccs, TLSChangeCipherSpec) = Verify TLSFinished +from scapy.layers.tls.handshake import TLSFinished cfin = t6.payload.payload.msg[0] assert isinstance(cfin, TLSFinished) @@ -1036,6 +1059,8 @@ assert (t6.payload.payload.mac == b'\x10\xce\xca2\xb4\xc3m\xf1\x16c\xdb\xfc\x08\ + Read a vulnerable TLS session = Reading TLS vulnerable session - Decrypt data from using a compromised server key +from scapy.layers.tls.cert import PrivKeyRSA +from scapy.layers.tls.record import TLSApplicationData import os tmp = "/test/tls/pki/srv_key.pem" filename = os.path.abspath(os.path.join(os.path.dirname(__file__),"../")) + tmp @@ -1065,6 +1090,8 @@ t.getlayer(2).msg[0].data == b"To boldly go where no man has gone before...\n" + Build TLS packets = Building packets - Various default records +from scapy.layers.tls.handshake import TLSCertificate +from scapy.layers.tls.record import TLSAlert raw(TLS()) raw(TLSClientHello()) raw(TLSServerHello()) @@ -1077,6 +1104,17 @@ raw(TLSApplicationData()) == b"" = Building packets - ClientHello with automatic length computation + +from scapy.layers.tls.crypto.suites import (TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, +TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, +TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, +TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, TLS_DHE_RSA_WITH_AES_128_CBC_SHA, +TLS_DHE_RSA_WITH_AES_256_CBC_SHA, TLS_RSA_WITH_AES_128_CBC_SHA, +TLS_RSA_WITH_AES_256_CBC_SHA, TLS_RSA_WITH_3DES_EDE_CBC_SHA) + +from scapy.layers.tls.extensions import (ServerName, TLS_Ext_SupportedEllipticCurves, +ProtocolName) + ch = TLSClientHello() ch.msgtype = 'client_hello' ch.version = 'TLS 1.2' @@ -1140,6 +1178,7 @@ assert a.haslayer(Raw) = Building packets - Perform dummy session update +from scapy.layers.tls.handshake import TLSHelloRequest assert not TLSHelloRequest().tls_session_update(None) @@ -1170,6 +1209,7 @@ assert TLSServerHello in pkt ############################################################################### = Test tlsSession +from scapy.layers.tls.session import tlsSession s = tlsSession(ipsrc="216.58.201.227", ipdst="127.0.0.1", sport=443, dport=443, sid=1) assert s.__repr__() == "216.58.201.227:443 > 127.0.0.1:443" assert s == s diff --git a/test/tls/example_client.py b/test/tls/example_client.py index 31a1fcefd2b..e828b63c2fe 100755 --- a/test/tls/example_client.py +++ b/test/tls/example_client.py @@ -1,7 +1,7 @@ #!/usr/bin/env python -## This file is part of Scapy -## This program is published under a GPLv2 license +# This file is part of Scapy +# This program is published under a GPLv2 license """ Basic TLS client. A ciphersuite may be commanded via a first argument. @@ -10,25 +10,59 @@ For instance, "sudo ./client_simple.py c014" will try to connect to any TLS server at 127.0.0.1:4433, with suite TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA. """ - import os import sys -basedir = os.path.abspath(os.path.join(os.path.dirname(__file__),"../../")) -sys.path=[basedir]+sys.path +basedir = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../")) +sys.path = [basedir] + sys.path from scapy.layers.tls.automaton_cli import TLSClientAutomaton -from scapy.layers.tls.handshake import TLSClientHello +from scapy.layers.tls.basefields import _tls_version_options + +from argparse import ArgumentParser + +psk = None +session_ticket_file = None +parser = ArgumentParser(description='Simple TLS Client') +parser.add_argument("--psk", + help="External PSK for symmetric authentication (for TLS 1.3)") # noqa: E501 +parser.add_argument("--res_master", + help="Resumption master secret (for TLS 1.3)") +parser.add_argument("--ticket_in", dest='session_ticket_file_in', + help="File to read a ticket from (for TLS 1.3)") +parser.add_argument("--ticket_out", dest='session_ticket_file_out', + help="File to write a ticket to (for TLS 1.3)") +parser.add_argument("--no_pfs", action="store_true", + help="Disable (EC)DHE exchange with PFS") +parser.add_argument("--early_data", help="File to read early_data to send") +parser.add_argument("--ciphersuite", help="Ciphersuite preference") +parser.add_argument("--curve", help="Group to advertise (ECC)") +parser.add_argument("--version", help="TLS Version", default="tls13") +args = parser.parse_args() -if len(sys.argv) == 2: - ch = TLSClientHello(ciphers=int(sys.argv[1], 16)) +# By default, PFS is set +if args.no_pfs: + psk_mode = "psk_ke" else: - ch = None + psk_mode = "psk_dhe_ke" -t = TLSClientAutomaton(client_hello=ch, - version="tls13-d18", +v = _tls_version_options.get(args.version, None) +if not v: + sys.exit("Unrecognized TLS version option.") + + +t = TLSClientAutomaton(client_hello=None, + version=args.version, mycert=basedir+"/test/tls/pki/cli_cert.pem", - mykey=basedir+"/test/tls/pki/cli_key.pem") + mykey=basedir+"/test/tls/pki/cli_key.pem", + psk=args.psk, + psk_mode=psk_mode, + resumption_master_secret=args.res_master, + session_ticket_file_in=args.session_ticket_file_in, + session_ticket_file_out=args.session_ticket_file_out, + early_data_file=args.early_data, + ciphersuite=args.ciphersuite, + curve=args.curve + ) t.run() - diff --git a/test/tls/example_server.py b/test/tls/example_server.py index ed740aa74e9..fe9476e675d 100755 --- a/test/tls/example_server.py +++ b/test/tls/example_server.py @@ -1,7 +1,7 @@ #!/usr/bin/env python -## This file is part of Scapy -## This program is published under a GPLv2 license +# This file is part of Scapy +# This program is published under a GPLv2 license """ Basic TLS server. A preferred ciphersuite may be provided as first argument. @@ -14,19 +14,46 @@ import os import sys -basedir = os.path.abspath(os.path.join(os.path.dirname(__file__),"../../")) -sys.path=[basedir]+sys.path +basedir = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../")) +sys.path = [basedir] + sys.path from scapy.layers.tls.automaton_srv import TLSServerAutomaton - - -if len(sys.argv) == 2: - pcs = int(sys.argv[1], 16) +from argparse import ArgumentParser + +psk = None +session_ticket_path = None + +parser = ArgumentParser(description='Simple TLS Client') +parser.add_argument("--psk", + help="External PSK for symmetric authentication (for TLS 1.3)") # noqa: E501 +parser.add_argument("--ticket_file", dest='session_ticket_file', + help="File to write/read a ticket to (for TLS 1.3)") +parser.add_argument("--no_pfs", action="store_true", + help="Disable (EC)DHE exchange with PFS") +parser.add_argument("--early_data", action="store_true", + help="Attempt to read 0-RTT data") +parser.add_argument("--client_auth", action="store_true", + help="Require client authentication") +parser.add_argument("--curve", help="Group to advertise (ECC)") +parser.add_argument("--cookie", action="store_true", + help="Send cookie extension in HelloRetryRequest message") +args = parser.parse_args() + +pcs = None + +# PFS is set by default... +if args.no_pfs and args.psk: + psk_mode = "psk_ke" else: - pcs = None + psk_mode = "psk_dhe_ke" t = TLSServerAutomaton(mycert=basedir+'/test/tls/pki/srv_cert.pem', mykey=basedir+'/test/tls/pki/srv_key.pem', - preferred_ciphersuite=pcs) + psk=args.psk, + preferred_ciphersuite=pcs, + client_auth=args.client_auth, + session_ticket_file=args.session_ticket_file, + early_data=args.early_data, + curve=args.curve, + cookie=args.cookie) t.run() - diff --git a/test/tls13.uts b/test/tls13.uts index f8aa9b66190..68e3874bf41 100644 --- a/test/tls13.uts +++ b/test/tls13.uts @@ -4,263 +4,2343 @@ # bash test/run_tests -t test/tls13.uts -F -+ Read a TLS 1.3 session ++ Read a protected TLS 1.3 session # /!\ These tests will not catch our 'INTEGRITY CHECK FAILED's. /!\ # We deem the knowledge of the plaintext sufficient for passing... -= Reading TLS 1.3 test session (vectors 5 from draft-ietf-tls-tls13-vectors-00) -~ crypto -import binascii -from cryptography.hazmat.primitives.asymmetric.ec import EllipticCurvePrivateNumbers -from cryptography.hazmat.backends import default_backend +#~ crypto + + + += Reading test session - Loading unparsed TLS 1.3 records +import binascii def clean(s): - return binascii.unhexlify(''.join(c for c in s if c.isalnum())) + return binascii.unhexlify(''.join(c for c in s if c.isalnum())) -clientHello1 = clean(""" - 16030100ae010000 aa0303d9e9898df6 - 3d43adbe64a2634f 0b63bcdc4019a3e5 26bc013a6042e05b - 14555c0000061301 130313020100007b 0000000b00090000 - 06736572766572ff 01000100000a0008 0006001d00170018 - 002800260024001d 002005efa94d13f5 adcd14219379d5a3 - 7dbce4721d9294e5 72c6651aeb761838 815b002b0003027f - 12000d0020001e04 0305030603020308 0408050806040105 - 0106010201040205 0206020202002d00 020101 +clientHello = clean(""" + 16 03 01 00 c4 01 00 00 c0 03 03 cb + 34 ec b1 e7 81 63 ba 1c 38 c6 da cb 19 6a 6d ff a2 1a 8d 99 12 + ec 18 a2 ef 62 83 02 4d ec e7 00 00 06 13 01 13 03 13 02 01 00 + 00 91 00 00 00 0b 00 09 00 00 06 73 65 72 76 65 72 ff 01 00 01 + 00 00 0a 00 14 00 12 00 1d 00 17 00 18 00 19 01 00 01 01 01 02 + 01 03 01 04 00 23 00 00 00 33 00 26 00 24 00 1d 00 20 99 38 1d + e5 60 e4 bd 43 d2 3d 8e 43 5a 7d ba fe b3 c0 6e 51 c1 3c ae 4d + 54 13 69 1e 52 9a af 2c 00 2b 00 03 02 03 04 00 0d 00 20 00 1e + 04 03 05 03 06 03 02 03 08 04 08 05 08 06 04 01 05 01 06 01 02 + 01 04 02 05 02 06 02 02 02 00 2d 00 02 01 01 00 1c 00 02 40 01 + """) + + +serverHello = clean(""" + 16 03 03 00 5a 02 00 00 56 03 03 a6 + af 06 a4 12 18 60 dc 5e 6e 60 24 9c d3 4c 95 93 0c 8a c5 cb 14 + 34 da c1 55 77 2e d3 e2 69 28 00 13 01 00 00 2e 00 33 00 24 00 + 1d 00 20 c9 82 88 76 11 20 95 fe 66 76 2b db f7 c6 72 e1 56 d6 + cc 25 3b 83 3d f1 dd 69 b1 b0 4e 75 1f 0f 00 2b 00 02 03 04 + """) + +serverEncHS = clean(""" + 17 03 03 02 a2 d1 ff 33 4a 56 f5 bf + f6 59 4a 07 cc 87 b5 80 23 3f 50 0f 45 e4 89 e7 f3 3a f3 5e df + 78 69 fc f4 0a a4 0a a2 b8 ea 73 f8 48 a7 ca 07 61 2e f9 f9 45 + cb 96 0b 40 68 90 51 23 ea 78 b1 11 b4 29 ba 91 91 cd 05 d2 a3 + 89 28 0f 52 61 34 aa dc 7f c7 8c 4b 72 9d f8 28 b5 ec f7 b1 3b + d9 ae fb 0e 57 f2 71 58 5b 8e a9 bb 35 5c 7c 79 02 07 16 cf b9 + b1 18 3e f3 ab 20 e3 7d 57 a6 b9 d7 47 76 09 ae e6 e1 22 a4 cf + 51 42 73 25 25 0c 7d 0e 50 92 89 44 4c 9b 3a 64 8f 1d 71 03 5d + 2e d6 5b 0e 3c dd 0c ba e8 bf 2d 0b 22 78 12 cb b3 60 98 72 55 + cc 74 41 10 c4 53 ba a4 fc d6 10 92 8d 80 98 10 e4 b7 ed 1a 8f + d9 91 f0 6a a6 24 82 04 79 7e 36 a6 a7 3b 70 a2 55 9c 09 ea d6 + 86 94 5b a2 46 ab 66 e5 ed d8 04 4b 4c 6d e3 fc f2 a8 94 41 ac + 66 27 2f d8 fb 33 0e f8 19 05 79 b3 68 45 96 c9 60 bd 59 6e ea + 52 0a 56 a8 d6 50 f5 63 aa d2 74 09 96 0d ca 63 d3 e6 88 61 1e + a5 e2 2f 44 15 cf 95 38 d5 1a 20 0c 27 03 42 72 96 8a 26 4e d6 + 54 0c 84 83 8d 89 f7 2c 24 46 1a ad 6d 26 f5 9e ca ba 9a cb bb + 31 7b 66 d9 02 f4 f2 92 a3 6a c1 b6 39 c6 37 ce 34 31 17 b6 59 + 62 22 45 31 7b 49 ee da 0c 62 58 f1 00 d7 d9 61 ff b1 38 64 7e + 92 ea 33 0f ae ea 6d fa 31 c7 a8 4d c3 bd 7e 1b 7a 6c 71 78 af + 36 87 90 18 e3 f2 52 10 7f 24 3d 24 3d c7 33 9d 56 84 c8 b0 37 + 8b f3 02 44 da 8c 87 c8 43 f5 e5 6e b4 c5 e8 28 0a 2b 48 05 2c + f9 3b 16 49 9a 66 db 7c ca 71 e4 59 94 26 f7 d4 61 e6 6f 99 88 + 2b d8 9f c5 08 00 be cc a6 2d 6c 74 11 6d bd 29 72 fd a1 fa 80 + f8 5d f8 81 ed be 5a 37 66 89 36 b3 35 58 3b 59 91 86 dc 5c 69 + 18 a3 96 fa 48 a1 81 d6 b6 fa 4f 9d 62 d5 13 af bb 99 2f 2b 99 + 2f 67 f8 af e6 7f 76 91 3f a3 88 cb 56 30 c8 ca 01 e0 c6 5d 11 + c6 6a 1e 2a c4 c8 59 77 b7 c7 a6 99 9b bf 10 dc 35 ae 69 f5 51 + 56 14 63 6c 0b 9b 68 c1 9e d2 e3 1c 0b 3b 66 76 30 38 eb ba 42 + f3 b3 8e dc 03 99 f3 a9 f2 3f aa 63 97 8c 31 7f c9 fa 66 a7 3f + 60 f0 50 4d e9 3b 5b 84 5e 27 55 92 c1 23 35 ee 34 0b bc 4f dd + d5 02 78 40 16 e4 b3 be 7e f0 4d da 49 f4 b4 40 a3 0c b5 d2 af + 93 98 28 fd 4a e3 79 4e 44 f9 4d f5 a6 31 ed e4 2c 17 19 bf da + bf 02 53 fe 51 75 be 89 8e 75 0e dc 53 37 0d 2b + """) + +clientEncHS = clean(""" + 17 03 03 00 35 75 ec 4d c2 38 cc e6 + 0b 29 80 44 a7 1e 21 9c 56 cc 77 b0 51 7f e9 b9 3c 7a 4b fc 44 + d8 7f 38 f8 03 38 ac 98 fc 46 de b3 84 bd 1c ae ac ab 68 67 d7 + 26 c4 05 46 + """) + + += Reading TLS 1.3 session - TLS parsing (no encryption) does not throw any error +# We will need to distinguish between connection ends. See next XXX below. +from scapy.layers.tls.record import TLS +t1 = TLS(clientHello) +t2 = TLS(serverHello, tls_session=t1.tls_session.mirror()) + += Reading TLS 1.3 session - TLS Record header +# We leave the possibility for some attributes to be either '' or None. +assert(t1.type == 0x16) +assert(t1.version == 0x0301) +assert(t1.len == 196) +assert(not t1.iv) +assert(not t1.mac) +assert(not t1.pad and not t1.padlen) +len(t1.msg) == 1 + + += Reading TLS 1.3 session - TLS Record __getitem__ +from scapy.layers.tls.handshake import TLSClientHello +TLSClientHello in t1 + + += Reading TLS 1.3 session - ClientHello +ch = t1.msg[0] +assert(isinstance(ch, TLSClientHello)) +assert(ch.msgtype == 1) +assert(ch.msglen == 192) +assert(ch.version == 0x0303) +assert(ch.gmt_unix_time == 0xcb34ecb1) +assert(ch.random_bytes == b'\xe7\x81c\xba\x1c8\xc6\xda\xcb\x19jm\xff\xa2\x1a\x8d\x99\x12\xec\x18\xa2\xefb\x83\x02M\xec\xe7') +assert(ch.sidlen == 0) +assert(not ch.sid) +assert(ch.cipherslen == 6) +assert(ch.ciphers == [4865, 4867, 4866]) +assert(ch.complen == 1) +assert(ch.comp == [0]) + + += Reading TLS 1.3 session - ClientHello extensions +from scapy.layers.tls.extensions import (TLS_Ext_ServerName, +TLS_Ext_RenegotiationInfo, TLS_Ext_SupportedGroups, +TLS_Ext_SessionTicket, TLS_Ext_SupportedVersion_CH, +TLS_Ext_SignatureAlgorithms, TLS_Ext_PSKKeyExchangeModes, +TLS_Ext_RecordSizeLimit) + +from scapy.layers.tls.keyexchange_tls13 import TLS_Ext_KeyShare_CH + +assert(ch.extlen == 145) +ext = ch.ext +assert(len(ext) == 9) +assert(isinstance(ext[0], TLS_Ext_ServerName)) +assert(ext[0].type == 0) +assert(ext[0].len == 11) +assert(ext[0].servernameslen == 9) +assert(len(ext[0].servernames) == 1) +assert(ext[0].servernames[0].nametype == 0) +assert(ext[0].servernames[0].namelen == 6) +assert(ext[0].servernames[0].servername == b"server") +assert(isinstance(ext[1], TLS_Ext_RenegotiationInfo)) +assert(not ext[1].renegotiated_connection) +assert(isinstance(ext[2], TLS_Ext_SupportedGroups)) +assert(ext[2].groups == [29, 23, 24, 25, 256, 257, 258, 259, 260]) +assert(isinstance(ext[3], TLS_Ext_SessionTicket)) +assert(not ext[3].ticket) +assert(isinstance(ext[4], TLS_Ext_KeyShare_CH)) +assert(ext[4].client_shares_len == 36) +assert(len(ext[4].client_shares) == 1) +assert(ext[4].client_shares[0].group == 29) +assert(ext[4].client_shares[0].kxlen == 32) +assert(ext[4].client_shares[0].key_exchange == b'\x998\x1d\xe5`\xe4\xbdC\xd2=\x8eCZ}\xba\xfe\xb3\xc0nQ\xc1<\xaeMT\x13i\x1eR\x9a\xaf,') +assert(isinstance(ext[5],TLS_Ext_SupportedVersion_CH)) +assert(ext[5].len == 3) +assert(ext[5].versionslen == 2) +assert(ext[5].versions == [772]) +assert(isinstance(ext[6], TLS_Ext_SignatureAlgorithms)) +assert(ext[6].sig_algs_len == 30) +assert(len(ext[6].sig_algs) == 15) +assert(ext[6].sig_algs[0] == 1027) +assert(ext[6].sig_algs[-1] == 514) +assert(isinstance(ext[7], TLS_Ext_PSKKeyExchangeModes)) +assert(ext[7].kxmodeslen == 1) +assert(ext[7].kxmodes[0] == 1) +assert(isinstance(ext[8], TLS_Ext_RecordSizeLimit)) +assert(ext[8].record_size_limit == 16385) + + += Reading TLS 1.3 session - ServerHello +from scapy.layers.tls.handshake import TLS13ServerHello +from scapy.layers.tls.extensions import TLS_Ext_SupportedVersion_SH +from scapy.layers.tls.keyexchange_tls13 import TLS_Ext_KeyShare_SH + +assert(TLS13ServerHello in t2) +sh = t2.msg[0] +ext = sh.ext +assert(isinstance(sh, TLS13ServerHello)) +assert(sh.random_bytes == b'\xa6\xaf\x06\xa4\x12\x18`\xdc^n`$\x9c\xd3L\x95\x93\x0c\x8a\xc5\xcb\x144\xda\xc1Uw.\xd3\xe2i(') +assert(sh.cipher == 0x1301) +assert(len(sh.ext) == 2) +assert(isinstance(ext[0], TLS_Ext_KeyShare_SH)) +assert(ext[0].len == 36) +assert(ext[0].server_share.group == 29) +assert(ext[0].server_share.key_exchange == b'\xc9\x82\x88v\x11 \x95\xfefv+\xdb\xf7\xc6r\xe1V\xd6\xcc%;\x83=\xf1\xddi\xb1\xb0Nu\x1f\x0f') +assert(isinstance(ext[1], TLS_Ext_SupportedVersion_SH)) +assert(ext[1].version == 0x0304) + + += Reading TLS 1.3 session - TLS parsing (with encryption) does not throw any error +from scapy.layers.tls.record_tls13 import TLS13 +t3 = TLS13(serverEncHS, tls_session=t2.tls_session) + + += Reading TLS 1.3 session - TLS13 Record header +assert(t3.type == 0x17) +assert(t3.version == 0x0303) +assert(t3.len == 674) + + += Reading TLS 1.3 session - TLS13 Record __getitem__ +TLS13 in t3 + += Reading TLS 1.3 session - TLS13 ApplicationData +from scapy.layers.tls.record_tls13 import TLSInnerPlaintext +TLSInnerPlaintext in t3 +assert(len(t3.auth_tag) == 16) +assert(t3.auth_tag == b'\xbf\x02S\xfeQu\xbe\x89\x8eu\x0e\xdcS7\r+') + + ++ Decrypt a TLS 1.3 session + += Decrypt a TLS 1.3 session - Parse client Hello +from scapy.layers.tls.extensions import TLS_Ext_SessionTicket +# Values from RFC8448, section 3 +x25519_clt_priv = clean(""" + 49 af 42 ba 7f 79 94 85 2d 71 3e f2 78 + 4b cb ca a7 91 1d e2 6a dc 56 42 cb 63 45 40 e7 ea 50 05 + """) + +x25519_clt_pub = clean(""" + 99 38 1d e5 60 e4 bd 43 d2 3d 8e 43 5a 7d + ba fe b3 c0 6e 51 c1 3c ae 4d 54 13 69 1e 52 9a af 2c + """) + +t = TLS(clientHello) +assert(len(t.msg) == 1) +assert(t.msg[0].msgtype == 1) +assert(t.msg[0].extlen == 145) +assert(len(t.msg[0].ext) == 9) +e = t.msg[0].ext +assert(isinstance(e[0], TLS_Ext_ServerName)) +assert(isinstance(e[1], TLS_Ext_RenegotiationInfo)) +assert(isinstance(e[2], TLS_Ext_SupportedGroups)) +assert(isinstance(e[3],TLS_Ext_SessionTicket)) +assert(e[3].len == 0) +assert(isinstance(e[4], TLS_Ext_KeyShare_CH)) +assert(len(e[4].client_shares) == 1) +assert(e[4].client_shares[0].group == 29) +assert(e[4].client_shares[0].key_exchange == x25519_clt_pub) +assert(isinstance(e[5], TLS_Ext_SupportedVersion_CH)) +assert(isinstance(e[6], TLS_Ext_SignatureAlgorithms)) +assert(isinstance(e[7], TLS_Ext_PSKKeyExchangeModes)) +assert(e[7].kxmodeslen == 1) +assert(len(e[7].kxmodes) == 1) +assert(e[7].kxmodes[0] == 1) +assert(isinstance(e[8], TLS_Ext_RecordSizeLimit)) + + += Decrypt a TLS 1.3 session - Parse server Hello +from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey +from scapy.layers.tls.crypto.pkcs1 import pkcs_os2ip + +# Values from RFC8448, section 3 +x25519_srv_priv = clean(""" + b1 58 0e ea df 6d d5 89 b8 ef 4f 2d 56 + 52 57 8c c8 10 e9 98 01 91 ec 8d 05 83 08 ce a2 16 a2 1e + """) + +x25519_srv_pub = clean(""" + c9 82 88 76 11 20 95 fe 66 76 2b db f7 c6 + 72 e1 56 d6 cc 25 3b 83 3d f1 dd 69 b1 b0 4e 75 1f 0f + """) + +privkey = X25519PrivateKey.from_private_bytes(x25519_clt_priv) +t.tls_session.tls13_client_privshares["x25519"] = privkey + +t = TLS(serverHello, tls_session=t.tls_session.mirror()) + +assert(len(t.msg) == 1) +assert(isinstance(t.msg[0], TLS13ServerHello)) +assert(len(t.msg[0].ext) == 2) +e = t.msg[0].ext +assert(isinstance(e[0], TLS_Ext_KeyShare_SH)) +assert(e[0].server_share.group == 29) +assert(e[0].server_share.key_exchange == x25519_srv_pub) +assert(isinstance(e[1], TLS_Ext_SupportedVersion_SH)) + += Decrypt a TLS 1.3 session - Handshake traffic secret derivation +# Values from RFC8448, section 3 +early_secret = clean(""" + 33 ad 0a 1c 60 7e c0 3b 09 e6 cd 98 93 68 0c + e2 10 ad f3 00 aa 1f 26 60 e1 b2 2e 10 f1 70 f9 2a + """) + +ecdhe_secret = clean(""" + 8b d4 05 4f b5 5b 9d 63 fd fb ac f9 f0 4b 9f 0d + 35 e6 d6 3f 53 75 63 ef d4 62 72 90 0f 89 49 2d + """) + +handshake_secret = clean(""" + 1d c8 26 e9 36 06 aa 6f dc 0a ad c1 2f 74 1b + 01 04 6a a6 b9 9f 69 1e d2 21 a9 f0 ca 04 3f be ac + """) + +client_handshake_traffic_secret = clean(""" + b3 ed db 12 6e 06 7f 35 a7 80 b3 ab f4 5e + 2d 8f 3b 1a 95 07 38 f5 2e 96 00 74 6a 0e 27 a5 5a 21 + """) + +server_handshake_traffic_secret = clean(""" + b6 7b 7d 69 0c c1 6c 4e 75 e5 42 13 cb 2d + 37 b4 e9 c9 12 bc de d9 10 5d 42 be fd 59 d3 91 ad 38 + """) + +assert(len(t.tls_session.tls13_derived_secrets) == 5) +assert(t.tls_session.tls13_early_secret is not None) +assert(t.tls_session.tls13_early_secret == early_secret) +assert(t.tls_session.tls13_dhe_secret == ecdhe_secret) +assert(t.tls_session.tls13_handshake_secret is not None) +assert(t.tls_session.tls13_handshake_secret == handshake_secret) +assert( 'client_handshake_traffic_secret' in t.tls_session.tls13_derived_secrets) +assert( t.tls_session.tls13_derived_secrets['client_handshake_traffic_secret'] == client_handshake_traffic_secret) +assert( 'server_handshake_traffic_secret' in t.tls_session.tls13_derived_secrets) +assert(t.tls_session.tls13_derived_secrets['server_handshake_traffic_secret'] == server_handshake_traffic_secret) + + += Decrypt a TLS 1.3 session - Server handshake traffic key calculation +# Values from RFC8448, section 3 +server_hs_traffic_key = clean(""" + 3f ce 51 60 09 c2 17 27 d0 f2 e4 e8 6e + e4 03 bc + """) + +server_hs_traffic_iv = clean(""" + 5d 31 3e b2 67 12 76 ee 13 00 0b 30 + """) + +assert(t.tls_session.prcs.cipher.key == server_hs_traffic_key) +assert(t.tls_session.prcs.cipher.fixed_iv == server_hs_traffic_iv) + + + += Decrypt a TLS 1.3 session - Decrypt and parse server encrypted handshake +# Values from RFC8448, section 3 +server_finished = clean(""" + 88 63 e6 bf b0 42 0a 92 7f a2 7f 34 33 6a + 70 ae 42 6e 96 8e 3e b8 84 94 5b 96 85 6d ba 39 76 d1 + """) + +t = TLS13(serverEncHS, tls_session=t.tls_session) +assert(t.deciphered_len == 658) +assert(t.inner.type == 22) +assert(len(t.inner.msg) == 4) +assert(t.auth_tag == b'\xbf\x02S\xfeQu\xbe\x89\x8eu\x0e\xdcS7\r+') + +m = t.inner.msg + += Decrypt a TLS 1.3 session - Parse decrypted EncryptedExtension +from scapy.layers.tls.handshake import TLSEncryptedExtensions +assert(isinstance(m[0], TLSEncryptedExtensions)) +assert(m[0].msgtype == 8) +assert(m[0].msglen == 36) +assert(m[0].extlen == 34) +assert(len(m[0].ext) == 3) +assert(isinstance(m[0].ext[0], TLS_Ext_SupportedGroups)) +assert(m[0].ext[0].groupslen == 18) +assert(m[0].ext[0].groups == [29, 23, 24, 25, 256, 257, 258, 259, 260]) +assert(isinstance(m[0].ext[1], TLS_Ext_RecordSizeLimit)) +assert(m[0].ext[1].record_size_limit == 16385) +assert(isinstance(m[0].ext[2], TLS_Ext_ServerName)) +assert(m[0].ext[2].len == 0) + += Decrypt a TLS 1.3 session - Parse decrypted TLS13Certificate +from scapy.layers.tls.cert import Cert +from scapy.layers.tls.handshake import (_ASN1CertAndExt, TLS13Certificate) + +assert(isinstance(m[1], TLS13Certificate)) +assert(m[1].msgtype, 11) +assert(m[1].msglen == 441) +assert(m[1].cert_req_ctxt_len == 0) +assert(m[1].cert_req_ctxt == b'') +assert(m[1].certslen == 437) +assert(len(m[1].certs) == 1) +assert(isinstance(m[1].certs[0], _ASN1CertAndExt)) +assert(m[1].certs[0].cert[0], 432) +assert(isinstance(m[1].certs[0].cert[1], Cert)) +assert(m[1].certs[0].cert[1].cA == False) +assert(m[1].certs[0].cert[1].isSelfSigned() == True) +assert(m[1].certs[0].cert[1].issuer['commonName'] == 'rsa') +assert(m[1].certs[0].cert[1].keyUsage == ['digitalSignature', 'keyEncipherment']) +assert(m[1].certs[0].cert[1].notAfter_str == 'Jul 30 01:23:59 2026 GMT') +assert(m[1].certs[0].cert[1].notBefore_str == 'Jul 30 01:23:59 2016 GMT') +assert(m[1].certs[0].cert[1].serial == 2) +assert(m[1].certs[0].cert[1].sigAlg == 'sha256WithRSAEncryption') +assert(m[1].certs[0].cert[1].signatureLen == 128) +assert(m[1].certs[0].cert[1].subject['commonName'] == 'rsa') +assert(m[1].certs[0].cert[1].version == 3) + + += Decrypt a TLS 1.3 session - Parse decrypted TLSCertificateVerify +from scapy.layers.tls.handshake import TLSCertificateVerify +from scapy.layers.tls.keyexchange import _TLSSignature +assert(isinstance(m[2], TLSCertificateVerify)) +assert(isinstance(m[2], TLSCertificateVerify)) +assert(m[2].msgtype == 15) +assert(m[2].msglen == 132) +assert(isinstance(m[2].sig, _TLSSignature)) +assert(m[2].sig.sig_alg == 2052) +assert(m[2].sig.sig_len == 128) +assert(m[2].sig.sig_val == b"Zt|]\x88\xfa\x9b\xd2\xe5Z\xb0\x85\xa6\x10\x15\xb7!\x1f\x82L\xd4\x84\x14Z\xb3\xffR\xf1\xfd\xa8G{\x0bz\xbc\x90\xdbx\xe2\xd3:\\\x14\x1a\x07\x86S\xfak\xefx\x0c^\xa2H\xee\xaa\xa7\x85\xc4\xf3\x94\xca\xb6\xd3\x0b\xbe\x8dHY\xeeQ\x1f`)W\xb1T\x11\xac\x02vqE\x9eFD\\\x9e\xa5\x8c\x18\x1e\x81\x8e\x95\xb8\xc3\xfb\x0b\xf3'\x84\t\xd3\xbe\x15*=\xa5\x04>\x06=\xdae\xcd\xf5\xae\xa2\rS\xdf\xac\xd4/t\xf3") + += Decrypt a TLS 1.3 session - Parse decrypted TLSFinished +from scapy.layers.tls.handshake import TLSFinished +# Values from RFC8448, section 3 +server_finished = clean(""" + 9b 9b 14 1d 90 63 37 fb d2 cb dc e7 1d f4 + de da 4a b4 2c 30 95 72 cb 7f ff ee 54 54 b7 8f 07 18 + """) +assert(isinstance(m[3], TLSFinished)) +assert(m[3].msgtype == 20) +assert(m[3].msglen == 32) +assert(m[3].vdata == server_finished) + + += Decrypt a TLS 1.3 session - Client handshake traffic key calculation + +# Values from RFC8448, section 3 +client_hs_traffic_key = clean(""" + db fa a6 93 d1 76 2c 5b 66 6a f5 d9 50 + 25 8d 01 + """) +client_hs_traffic_iv = clean(""" + 5b d3 c7 1b 83 6e 0b 76 bb 73 26 5f + """) + +assert(t.tls_session.pwcs.cipher.key == client_hs_traffic_key) +assert(t.tls_session.pwcs.cipher.fixed_iv == client_hs_traffic_iv) + += Decrypt a TLS 1.3 session - Decrypt and parse client encrypted handshake +# Values from RFC8448, section 3 +client_finished = clean(""" + a8 ec 43 6d 67 76 34 ae 52 5a c1 fc eb e1 + 1a 03 9e c1 76 94 fa c6 e9 85 27 b6 42 f2 ed d5 ce 61 + """) + +t = TLS13(clientEncHS, tls_session=t.tls_session.mirror()) +assert(t.deciphered_len == 37) +assert(t.inner.type == 22) +assert(len(t.inner.msg) == 1) +m = t.inner.msg +assert(isinstance(m[0], TLSFinished)) +assert(m[0].vdata == client_finished) + += Decrypt a TLS 1.3 session - Application traffic secret derivation +# Values from RFC8448, section 3 +master_secret = clean(""" + 18 df 06 84 3d 13 a0 8b f2 a4 49 84 4c 5f 8a + 47 80 01 bc 4d 4c 62 79 84 d5 a4 1d a8 d0 40 29 19 + """) + +client_application_traffic_secret_0 = clean(""" + 9e 40 64 6c e7 9a 7f 9d c0 5a f8 88 9b ce + 65 52 87 5a fa 0b 06 df 00 87 f7 92 eb b7 c1 75 04 a5 + """) + +server_application_traffic_secret_0 = clean(""" + a1 1a f9 f0 55 31 f8 56 ad 47 11 6b 45 a9 + 50 32 82 04 b4 f4 4b fb 6b 3a 4b 4f 1f 3f cb 63 16 43 + """) + + +exporter_master_secret = clean(""" + fe 22 f8 81 17 6e da 18 eb 8f 44 52 9e 67 + 92 c5 0c 9a 3f 89 45 2f 68 d8 ae 31 1b 43 09 d3 cf 50 + """) + +resumption_master_secret = clean(""" + 7d f2 35 f2 03 1d 2a 05 12 87 d0 2b 02 41 + b0 bf da f8 6c c8 56 23 1f 2d 5a ba 46 c4 34 ec 19 6c + """) + + +assert(t.tls_session.tls13_master_secret is not None) +assert(t.tls_session.tls13_master_secret == master_secret) + +assert(len(t.tls_session.tls13_derived_secrets) == 9) +assert('client_traffic_secrets' in t.tls_session.tls13_derived_secrets) +assert(len(t.tls_session.tls13_derived_secrets['client_traffic_secrets']) == 1) +assert(t.tls_session.tls13_derived_secrets['client_traffic_secrets'][0] == client_application_traffic_secret_0) + +assert('server_traffic_secrets' in t.tls_session.tls13_derived_secrets) +assert(len(t.tls_session.tls13_derived_secrets['server_traffic_secrets']) == 1) +assert(t.tls_session.tls13_derived_secrets['server_traffic_secrets'][0] == server_application_traffic_secret_0) + +assert('exporter_secret' in t.tls_session.tls13_derived_secrets) +assert(t.tls_session.tls13_derived_secrets['exporter_secret'] == exporter_master_secret) + +assert('resumption_secret' in t.tls_session.tls13_derived_secrets) +assert(t.tls_session.tls13_derived_secrets['resumption_secret'] == resumption_master_secret) + += Decrypt a TLS 1.3 session - Application traffic keys calculation + +# Values from RFC8448, section 3 +client_ap_traffic_key = clean(""" + 17 42 2d da 59 6e d5 d9 ac d8 90 e3 c6 + 3f 50 51 + """) + +client_ap_traffic_iv = clean(""" + 5b 78 92 3d ee 08 57 90 33 e5 23 d9 + """) + +server_ap_traffic_key = clean(""" + 9f 02 28 3b 6c 9c 07 ef c2 6b b9 f2 ac + 92 e3 56 + """) + +server_ap_traffic_iv = clean(""" + cf 78 2b 88 dd 83 54 9a ad f1 e9 84 + """) + +# wcs instead of pwcs ? +assert(t.tls_session.rcs.cipher.key == client_ap_traffic_key) +assert(t.tls_session.rcs.cipher.fixed_iv == client_ap_traffic_iv) +assert(t.tls_session.wcs.cipher.key == server_ap_traffic_key) +assert(t.tls_session.wcs.cipher.fixed_iv == server_ap_traffic_iv) + += Decrypt a TLS 1.3 session - Decrypt and parse server NewSessionTicket +from scapy.layers.tls.extensions import TLS_Ext_EarlyDataIndicationTicket +# Value from RFC8448, section 3 +serverEncTicket = clean(""" + 17 03 03 00 de 3a 6b 8f 90 41 4a 97 + d6 95 9c 34 87 68 0d e5 13 4a 2b 24 0e 6c ff ac 11 6e 95 d4 1d + 6a f8 f6 b5 80 dc f3 d1 1d 63 c7 58 db 28 9a 01 59 40 25 2f 55 + 71 3e 06 1d c1 3e 07 88 91 a3 8e fb cf 57 53 ad 8e f1 70 ad 3c + 73 53 d1 6d 9d a7 73 b9 ca 7f 2b 9f a1 b6 c0 d4 a3 d0 3f 75 e0 + 9c 30 ba 1e 62 97 2a c4 6f 75 f7 b9 81 be 63 43 9b 29 99 ce 13 + 06 46 15 13 98 91 d5 e4 c5 b4 06 f1 6e 3f c1 81 a7 7c a4 75 84 + 00 25 db 2f 0a 77 f8 1b 5a b0 5b 94 c0 13 46 75 5f 69 23 2c 86 + 51 9d 86 cb ee ac 87 aa c3 47 d1 43 f9 60 5d 64 f6 50 db 4d 02 + 3e 70 e9 52 ca 49 fe 51 37 12 1c 74 bc 26 97 68 7e 24 87 46 d6 + df 35 30 05 f3 bc e1 86 96 12 9c 81 53 55 6b 3b 6c 67 79 b3 7b + f1 59 85 68 4f """) +t = TLS13(serverEncTicket, tls_session=t.tls_session.mirror()) + +assert(t.deciphered_len == 206) +assert(t.inner.type == 22) +assert(t.auth_tag == b'\x9c\x81SUk;lgy\xb3{\xf1Y\x85hO') +assert(len(t.inner.msg) == 1) +m = t.inner.msg[0] +assert(m.msgtype == 4) +assert(m.ticket_lifetime == 30) +assert(m.ticket_age_add == 4208372421) +assert(m.noncelen == 2) +assert(len(m.ticket_nonce) == 2) +assert(m.ticket_nonce == b'\x00\x00') +assert(m.ticket == b',\x03]\x82\x93Y\xee_\xf7\xafN\xc9\x00\x00\x00\x00&*d\x94\xdcHm,\x8a4\xcb3\xfa\x90\xbf\x1b\x00p\xad=\xa2g\x7f\xa5\x90l[?}\x8f\x92\xf2(\xbd\xa4\r\xdar\x14p\xf9\xfb\xf2\x97\xb5\xae\xa6\x17do\xac\\\x03\'.\x97\x07\'\xc6!\xa7\x91A\xef_}\xe6P^[\xfb\xc3\x88\xe93Ci@\x93\x93J\xe4\xd3W') +assert(len(m.ext) == 1) +assert(isinstance(m.ext[0], TLS_Ext_EarlyDataIndicationTicket)) +assert(m.ext[0].max_early_data_size == 1024) + + += Decrypt a TLS 1.3 session - Compute the PSK associated with the ticket +from scapy.layers.tls.crypto.hkdf import TLS13_HKDF +hash_len = t.tls_session.rcs.ciphersuite.hash_alg.hash_len +hkdf = TLS13_HKDF(t.tls_session.rcs.ciphersuite.hash_alg.name.lower()) +tls13_psk_secret = hkdf.expand_label(t.tls_session.tls13_derived_secrets['resumption_secret'], + b"resumption", + m.ticket_nonce, + hash_len) + +# Value from RFC8448, section 3 +psk_resumption = clean(""" + 4e cd 0e b6 ec 3b 4d 87 f5 d6 02 8f 92 2c + a4 c5 85 1a 27 7f d4 13 11 c9 e6 2d 2c 94 92 e1 c4 f3 + """) + +assert(hash_len == 32) +assert(tls13_psk_secret == psk_resumption) + += Decrypt a TLS 1.3 session - Decrypt and parse client Application Data +from scapy.layers.tls.record import TLSApplicationData +# Values from RFC8448, section 3 +payload = clean(""" + 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e + 0f 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 21 22 23 + 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 31 + """) + + +clientEncAppData = clean(""" + 17 03 03 00 43 a2 3f 70 54 b6 2c 94 + d0 af fa fe 82 28 ba 55 cb ef ac ea 42 f9 14 aa 66 bc ab 3f 2b + 98 19 a8 a5 b4 6b 39 5b d5 4a 9a 20 44 1e 2b 62 97 4e 1f 5a 62 + 92 a2 97 70 14 bd 1e 3d ea e6 3a ee bb 21 69 49 15 e4 + """) +t = TLS13(clientEncAppData, tls_session=t.tls_session.mirror()) + +assert(t.deciphered_len == 51) +assert(len(t.inner.msg) == 1) +assert(t.inner.type == 23) +m = t.inner.msg[0] +assert(isinstance(m, TLSApplicationData)) +assert(m.data == payload) + += Decrypt a TLS 1.3 session - Decrypt and parse server Application Data +# Values from RFC8448, section 3 +payload = clean(""" + 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e + 0f 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 21 22 23 + 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 31 + """) + +serverEncAppData = clean(""" + 17 03 03 00 43 2e 93 7e 11 ef 4a c7 + 40 e5 38 ad 36 00 5f c4 a4 69 32 fc 32 25 d0 5f 82 aa 1b 36 e3 + 0e fa f9 7d 90 e6 df fc 60 2d cb 50 1a 59 a8 fc c4 9c 4b f2 e5 + f0 a2 1c 00 47 c2 ab f3 32 54 0d d0 32 e1 67 c2 95 5d + """) + +t = TLS13(serverEncAppData, tls_session=t.tls_session.mirror()) +assert(t.deciphered_len == 51) +assert(len(t.inner.msg) == 1) +assert(t.inner.type == 23) +m = t.inner.msg[0] +assert(isinstance(m, TLSApplicationData)) +assert(m.data == payload) + += Decrypt a TLS 1.3 session - Decrypt client Alert +from scapy.layers.tls.record import TLSAlert +# Value from RFC8448, section 3 +clientEncAlert = clean(""" + 17 03 03 00 13 c9 87 27 60 65 56 66 + b7 4d 7f f1 15 3e fd 6d b6 d0 b0 e3 + """) + +t = TLS13(clientEncAlert, tls_session=t.tls_session.mirror()) +assert(t.deciphered_len == 3) +assert(len(t.inner.msg) == 1) +assert(t.inner.type == 21) +m = t.inner.msg[0] +assert(isinstance(m, TLSAlert)) +assert(m.level == 1) +assert(m.descr == 0) + += Decrypt a TLS 1.3 session - Decrypt server Alert +# Value from RFC8448, section 3 +serverEncAlert = clean(""" + 17 03 03 00 13 b5 8f d6 71 66 eb f5 + 99 d2 47 20 cf be 7e fa 7a 88 64 a9 + """) +t = TLS13(serverEncAlert, tls_session=t.tls_session.mirror()) +assert(t.deciphered_len == 3) +assert(len(t.inner.msg) == 1) +assert(t.inner.type == 21) +m = t.inner.msg[0] +assert(isinstance(m, TLSAlert)) +assert(m.level == 1) +assert(m.descr == 0) + + + + + + + + +########### Resumed 0-RTT Handshake ############################################### + ++ Decrypt a resumed TLS 1.3 session with 0-RTT + + += Decrypt a resumed TLS 1.3 session with 0-RTT - Parse ClientHello +from scapy.layers.tls.extensions import (TLS_Ext_EarlyDataIndication, + TLS_Ext_PSKKeyExchangeModes, + TLS_Ext_Padding) +from scapy.layers.tls.keyexchange_tls13 import (TLS_Ext_PreSharedKey_CH, + PSKBinderEntry) + +from scapy.layers.tls.session import tlsSession +# Values from RFC8448, section 4 + +x25519_clt_priv = clean(""" + 49 af 42 ba 7f 79 94 85 2d 71 3e f2 78 + 4b cb ca a7 91 1d e2 6a dc 56 42 cb 63 45 40 e7 ea 50 05 + """) + +x25519_clt_pub = clean(""" + e4 ff b6 8a c0 5f 8d 96 c9 9d a2 66 98 34 + 6c 6b e1 64 82 ba dd da fe 05 1a 66 b4 f1 8d 66 8f 0b + """) + +psk_binder = clean(""" + 3a dd 4f b2 d8 fd f8 22 a0 ca 3c f7 67 8e + f5 e8 8d ae 99 01 41 c5 92 4d 57 bb 6f a3 1b 9e 5f 9d + """) + +clientHello = clean(""" + 16 03 01 02 00 01 00 01 fc 03 03 1b + c3 ce b6 bb e3 9c ff 93 83 55 b5 a5 0a db 6d b2 1b 7a 6a f6 49 + d7 b4 bc 41 9d 78 76 48 7d 95 00 00 06 13 01 13 03 13 02 01 00 + 01 cd 00 00 00 0b 00 09 00 00 06 73 65 72 76 65 72 ff 01 00 01 + 00 00 0a 00 14 00 12 00 1d 00 17 00 18 00 19 01 00 01 01 01 02 + 01 03 01 04 00 33 00 26 00 24 00 1d 00 20 e4 ff b6 8a c0 5f 8d + 96 c9 9d a2 66 98 34 6c 6b e1 64 82 ba dd da fe 05 1a 66 b4 f1 + 8d 66 8f 0b 00 2a 00 00 00 2b 00 03 02 03 04 00 0d 00 20 00 1e + 04 03 05 03 06 03 02 03 08 04 08 05 08 06 04 01 05 01 06 01 02 + 01 04 02 05 02 06 02 02 02 00 2d 00 02 01 01 00 1c 00 02 40 01 + 00 15 00 57 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 29 00 dd 00 b8 00 b2 2c 03 5d 82 93 59 + ee 5f f7 af 4e c9 00 00 00 00 26 2a 64 94 dc 48 6d 2c 8a 34 cb + 33 fa 90 bf 1b 00 70 ad 3c 49 88 83 c9 36 7c 09 a2 be 78 5a bc + 55 cd 22 60 97 a3 a9 82 11 72 83 f8 2a 03 a1 43 ef d3 ff 5d d3 + 6d 64 e8 61 be 7f d6 1d 28 27 db 27 9c ce 14 50 77 d4 54 a3 66 + 4d 4e 6d a4 d2 9e e0 37 25 a6 a4 da fc d0 fc 67 d2 ae a7 05 29 + 51 3e 3d a2 67 7f a5 90 6c 5b 3f 7d 8f 92 f2 28 bd a4 0d da 72 + 14 70 f9 fb f2 97 b5 ae a6 17 64 6f ac 5c 03 27 2e 97 07 27 c6 + 21 a7 91 41 ef 5f 7d e6 50 5e 5b fb c3 88 e9 33 43 69 40 93 93 + 4a e4 d3 57 fa d6 aa cb 00 21 20 3a dd 4f b2 d8 fd f8 22 a0 ca + 3c f7 67 8e f5 e8 8d ae 99 01 41 c5 92 4d 57 bb 6f a3 1b 9e 5f + 9d + """) + +# Initialize a tlsSession with the resumption PSK and cipher from resumed +# session +s = tlsSession() +s.tls13_psk_secret = psk_resumption +s.tls13_ticket_ciphersuite = 0x1301 + +t = TLS(clientHello, tls_session = s) +assert(len(t.msg) == 1) +assert(t.msg[0].msgtype == 1) +assert(t.msg[0].extlen == 461) +assert(len(t.msg[0].ext) == 11) +e = t.msg[0].ext +assert(isinstance(e[0], TLS_Ext_ServerName)) +assert(isinstance(e[1], TLS_Ext_RenegotiationInfo)) +assert(isinstance(e[2], TLS_Ext_SupportedGroups)) +assert(isinstance(e[3],TLS_Ext_KeyShare_CH)) +assert(len(e[3].client_shares) == 1) +assert(e[3].client_shares[0].group == 29) +assert(e[3].client_shares[0].key_exchange == x25519_clt_pub) +assert(isinstance(e[4], TLS_Ext_EarlyDataIndication)) +assert(e[4].len == 0) +assert(isinstance(e[5], TLS_Ext_SupportedVersion_CH)) +assert(isinstance(e[6], TLS_Ext_SignatureAlgorithms)) +assert(isinstance(e[7], TLS_Ext_PSKKeyExchangeModes)) +assert(e[7].kxmodeslen == 1) +assert(len(e[7].kxmodes) == 1) +assert(e[7].kxmodes[0] == 1) +assert(isinstance(e[8], TLS_Ext_RecordSizeLimit)) +assert(isinstance(e[9], TLS_Ext_Padding)) +assert(e[9].len == 87) +assert(e[9].padding == b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') +assert(isinstance(e[10], TLS_Ext_PreSharedKey_CH)) +assert(e[10].len == 221) +assert(e[10].identities_len == 184) +assert(len(e[10].identities) == 1) +assert(e[10].identities[0].obfuscated_ticket_age == 4208372427) +assert(e[10].binders_len == 33) +assert(len(e[10].binders) == 1) +assert(isinstance(e[10].binders[0], PSKBinderEntry)) +assert(e[10].binders[0].binder_len == 32) +assert(e[10].binders[0].binder == psk_binder) + += Decrypt a resumed TLS 1.3 session with 0-RTT - Early traffic secret derivation + +# Values from RFC8448, section 4 +early_secret = clean(""" + 9b 21 88 e9 b2 fc 6d 64 d7 1d c3 29 90 0e 20 + bb 41 91 50 00 f6 78 aa 83 9c bb 79 7c b7 d8 33 2c + """) + +binder_key = clean(""" + 69 fe 13 1a 3b ba d5 d6 3c 64 ee bc c3 0e 39 5b + 9d 81 07 72 6a 13 d0 74 e3 89 db c8 a4 e4 72 56 + """) + +client_early_traffic_secret = clean(""" + 3f bb e6 a6 0d eb 66 c3 0a 32 79 5a ba 0e + ff 7e aa 10 10 55 86 e7 be 5c 09 67 8d 63 b6 ca ab 62 + """) + +early_exporter_master_secret = clean(""" + b2 02 68 66 61 09 37 d7 42 3e 5b e9 08 62 + cc f2 4c 0e 60 91 18 6d 34 f8 12 08 9f f5 be 2e f7 df + """) + +assert(t.tls_session.tls13_early_secret is not None) +assert(t.tls_session.tls13_early_secret == early_secret) + +assert(len(t.tls_session.tls13_derived_secrets) == 3) + +assert('binder_key' in t.tls_session.tls13_derived_secrets) +assert(t.tls_session.tls13_derived_secrets['binder_key'] == binder_key) + +assert('client_early_traffic_secret' in t.tls_session.tls13_derived_secrets) +assert(t.tls_session.tls13_derived_secrets['client_early_traffic_secret'] == client_early_traffic_secret) + +assert('early_exporter_secret' in t.tls_session.tls13_derived_secrets) +assert(t.tls_session.tls13_derived_secrets['early_exporter_secret'] == early_exporter_master_secret) + += Decrypt a resumed TLS 1.3 session with 0-RTT - Derive traffic keys for early data + +# Values from RFC8448, section 4 +early_app_data_traffic_key = clean(""" + 92 02 05 a5 b7 bf 21 15 e6 fc 5c 29 42 83 4f 54 + """) + +early_ap_data_fixed_iv = clean(""" + 6d 47 5f 09 93 c8 e5 64 61 0d b2 b9 + """) + +assert(t.tls_session.prcs.cipher.key_len == 16) +assert(t.tls_session.prcs.cipher.key == early_app_data_traffic_key) +assert(t.tls_session.prcs.cipher.fixed_iv_len == 12) +assert(t.tls_session.prcs.cipher.fixed_iv == early_ap_data_fixed_iv) + += Decrypt a resumed TLS 1.3 session with 0-RTT - Decrypt and parse early data +# Values from RFC8448, section 4 +early_data = clean(""" + 41 42 43 44 45 46 + """) + +clientEarlyData = clean(""" + 17 03 03 00 17 ab 1d f4 20 e7 5c 45 + 7a 7c c5 d2 84 4f 76 d5 ae e4 b4 ed bf 04 9b e0 + """) + +t = TLS13(clientEarlyData, tls_session = t.tls_session) + +assert(t.deciphered_len == 7) +assert(len(t.inner.msg) == 1) +assert(isinstance(t.inner.msg[0], TLSApplicationData)) +assert(t.inner.msg[0].data == early_data) +assert(t.inner.type == 23) + += Decrypt a resumed TLS 1.3 session with 0-RTT - Parse Server Hello +from scapy.layers.tls.keyexchange_tls13 import TLS_Ext_PreSharedKey_SH +# Values from RFC8448, section 4 +x25519_srv_priv = clean(""" + de 5b 44 76 e7 b4 90 b2 65 2d 33 8a cb + f2 94 80 66 f2 55 f9 44 0e 23 b9 8f c6 98 35 29 8d c1 07 + """) + +x25519_srv_pub = clean(""" + 12 17 61 ee 42 c3 33 e1 b9 e7 7b 60 dd 57 + c2 05 3c d9 45 12 ab 47 f1 15 e8 6e ff 50 94 2c ea 31 + """) + +serverHello = clean(""" + 16 03 03 00 60 02 00 00 5c 03 03 3c + cf d2 de c8 90 22 27 63 47 2a e8 13 67 77 c9 d7 35 87 77 bb 66 + e9 1e a5 12 24 95 f5 59 ea 2d 00 13 01 00 00 34 00 29 00 02 00 + 00 00 33 00 24 00 1d 00 20 12 17 61 ee 42 c3 33 e1 b9 e7 7b 60 + dd 57 c2 05 3c d9 45 12 ab 47 f1 15 e8 6e ff 50 94 2c ea 31 00 + 2b 00 02 03 04 + """) + + +# /!\ +# Not working when using X25519PrivateKey client priv_key : +# privkey = X25519PrivateKey.from_private_bytes(x25519_clt_priv) +# t.tls_session.tls13_client_privshares["x25519"] = privkey +# +# The assert(t.tls_session.tls13_dhe_secret == ecdhe_secret) fail ! +# Maybe a bug in the crypto library ? +# /!\ + +privkey = X25519PrivateKey.from_private_bytes(x25519_srv_priv) +t.tls_session.tls13_server_privshare["x25519"] = privkey +#privkey = X25519PrivateKey.from_private_bytes(x25519_clt_priv) +#t.tls_session.tls13_client_privshares["x25519"] = privkey + +t = TLS(serverHello, tls_session = t.tls_session.mirror()) + +assert(len(t.msg) == 1) +assert(isinstance(t.msg[0], TLS13ServerHello)) +assert(len(t.msg[0].ext) == 3) +e = t.msg[0].ext +assert(isinstance(e[0], TLS_Ext_PreSharedKey_SH)) +assert(e[0].selected_identity == 0) +assert(isinstance(e[1], TLS_Ext_KeyShare_SH)) +assert(e[1].server_share.group == 29) +assert(e[1].server_share.key_exchange == x25519_srv_pub) +assert(isinstance(e[2], TLS_Ext_SupportedVersion_SH)) + += Decrypt a resumed TLS 1.3 session with 0-RTT - Handshake traffic secret derivation + +# Values from RFC8448, section 3 +ecdhe_secret = clean(""" + f4 41 94 75 6f f9 ec 9d 25 18 06 35 d6 6e a6 82 + 4c 6a b3 bf 17 99 77 be 37 f7 23 57 0e 7c cb 2e + """) + +handshake_secret = clean(""" + 00 5c b1 12 fd 8e b4 cc c6 23 bb 88 a0 7c 64 + b3 ed e1 60 53 63 fc 7d 0d f8 c7 ce 4f f0 fb 4a e6 + """) + +client_handshake_traffic_secret = clean(""" + 2f aa c0 8f 85 1d 35 fe a3 60 4f cb 4d e8 + 2d c6 2c 9b 16 4a 70 97 4d 04 62 e2 7f 1a b2 78 70 0f + """) + +server_handshake_traffic_secret = clean(""" + fe 92 7a e2 71 31 2e 8b f0 27 5b 58 1c 54 + ee f0 20 45 0d c4 ec ff aa 05 a1 a3 5d 27 51 8e 78 03 + """) + +assert(len(t.tls_session.tls13_derived_secrets) == 5) +assert(t.tls_session.tls13_dhe_secret == ecdhe_secret) +assert(t.tls_session.tls13_handshake_secret is not None) +assert(t.tls_session.tls13_handshake_secret == handshake_secret) +assert( 'client_handshake_traffic_secret' in t.tls_session.tls13_derived_secrets) +assert( t.tls_session.tls13_derived_secrets['client_handshake_traffic_secret'] == client_handshake_traffic_secret) +assert( 'server_handshake_traffic_secret' in t.tls_session.tls13_derived_secrets) +assert(t.tls_session.tls13_derived_secrets['server_handshake_traffic_secret'] == server_handshake_traffic_secret) + += Decrypt a resumed TLS 1.3 session with 0-RTT - Server handshake traffic key calculation +# Values from RFC8448, section 4 +server_hs_traffic_key = clean(""" + 27 c6 bd c0 a3 dc ea 39 a4 73 26 d7 9b + c9 e4 ee + """) + +server_hs_traffic_iv = clean(""" + 95 69 ec dd 4d 05 36 70 5e 9e f7 25 + """) + +assert(t.tls_session.prcs.cipher.key == server_hs_traffic_key) +assert(t.tls_session.prcs.cipher.fixed_iv == server_hs_traffic_iv) + + += Decrypt a resumed TLS 1.3 session with 0-RTT - Decrypt and parse server encrypted handshake +# Value from RFC8448, section 4 +serverEncHS = clean(""" + 17 03 03 00 61 dc 48 23 7b 4b 87 9f + 50 d0 d4 d2 62 ea 8b 47 16 eb 40 dd c1 eb 95 7e 11 12 6e 8a 71 + 49 c2 d0 12 d3 7a 71 15 95 7e 64 ce 30 00 8b 9e 03 23 f2 c0 5a + 9c 1c 77 b4 f3 78 49 a6 95 ab 25 50 60 a3 3f ee 77 0c a9 5c b8 + 48 6b fd 08 43 b8 70 24 86 5c a3 5c c4 1c 4e 51 5c 64 dc b1 36 + 9f 98 63 5b c7 a5 + """) + +t = TLS13(serverEncHS, tls_session = t.tls_session) +assert(t.deciphered_len == 81) +assert(len(t.inner.msg) == 2) +assert(isinstance(t.inner.msg[0], TLSEncryptedExtensions)) +e = t.inner.msg[0].ext +assert(len(e) == 4) +assert(isinstance(e[0], TLS_Ext_SupportedGroups)) +assert(isinstance(e[1], TLS_Ext_RecordSizeLimit)) +assert(isinstance(e[2], TLS_Ext_ServerName)) +assert(isinstance(e[3], TLS_Ext_EarlyDataIndication)) +assert(e[3].len == 0) +assert(t.inner.type == 22) + + += Decrypt a resumed TLS 1.3 session with 0-RTT - Decrypt and parse client EndOfEarlyData +from scapy.layers.tls.handshake import TLS13EndOfEarlyData +# Value from RFC8448, section 4 +clientEncHs1 = clean(""" + 17 03 03 00 15 ac a6 fc 94 48 41 29 + 8d f9 95 93 72 5f 9b f9 75 44 29 b1 2f 09 + """) + +t = TLS13(clientEncHs1, tls_session = t.tls_session.mirror()) +assert(t.deciphered_len == 5) +assert(len(t.inner.msg) == 1) +assert(isinstance(t.inner.msg[0], TLS13EndOfEarlyData)) +assert(t.inner.msg[0].msgtype == 5) +assert(t.inner.msg[0].msglen == 0) +assert(t.inner.type == 22) + + += Decrypt a resumed TLS 1.3 session with 0-RTT - Decrypt and parse client Finished + +# Values from RFC8448, section 4 +client_finished = clean(""" + 72 30 a9 c9 52 c2 5c d6 13 8f c5 e6 62 83 + 08 c4 1c 53 35 dd 81 b9 f9 6b ce a5 0f d3 2b da 41 6d + """) + +clientEncHs2 = clean(""" + 17 03 03 00 35 00 f8 b4 67 d1 4c f2 + 2a 4b 3f 0b 6a e0 d8 e6 cc 8d 08 e0 db 35 15 ef 5c 2b df 19 22 + ea fb b7 00 09 96 47 16 d8 34 fb 70 c3 d2 a5 6c 5b 1f 5f 6b db + a6 c3 33 cf + """) + +t = TLS13(clientEncHs2, tls_session = t.tls_session) +assert(t.deciphered_len == 37) +assert(len(t.inner.msg) == 1) +assert(isinstance(t.inner.msg[0], TLSFinished)) +assert(t.inner.msg[0].vdata == client_finished) +assert(t.inner.type == 22) + += Decrypt a resumed TLS 1.3 session with 0-RTT - Application traffic secret derivation + +# Values from RFC8448, section 4 +master_secret = clean(""" + e2 d3 2d 4e d6 6d d3 78 97 a0 e8 0c 84 10 75 + 03 ce 58 bf 8a ad 4c b5 5a 50 02 d7 7e cb 89 0e ce + """) + +client_application_traffic_secret_0 = clean(""" + 2a bb f2 b8 e3 81 d2 3d be be 1d d2 a7 d1 + 6a 8b f4 84 cb 49 50 d2 3f b7 fb 7f a8 54 70 62 d9 a1 + """) + +server_application_traffic_secret_0 = clean(""" + cc 21 f1 bf 8f eb 7d d5 fa 50 5b d9 c4 b4 + 68 a9 98 4d 55 4a 99 3d c4 9e 6d 28 55 98 fb 67 26 91 + """) + + +exporter_master_secret = clean(""" + 3f d9 3d 4f fd dc 98 e6 4b 14 dd 10 7a ed + f8 ee 4a dd 23 f4 51 0f 58 a4 59 2d 0b 20 1b ee 56 b4 + """) + +resumption_master_secret = clean(""" + 5e 95 bd f1 f8 90 05 ea 2e 9a a0 ba 85 e7 + 28 e3 c1 9c 5f e0 c6 99 e3 f5 be e5 9f ae bd 0b 54 06 + """) + +assert(t.tls_session.tls13_master_secret is not None) +assert(t.tls_session.tls13_master_secret == master_secret) + +assert(len(t.tls_session.tls13_derived_secrets) == 9) +assert('client_traffic_secrets' in t.tls_session.tls13_derived_secrets) +assert(len(t.tls_session.tls13_derived_secrets['client_traffic_secrets']) == 1) +assert(t.tls_session.tls13_derived_secrets['client_traffic_secrets'][0] == client_application_traffic_secret_0) + +assert('server_traffic_secrets' in t.tls_session.tls13_derived_secrets) +assert(len(t.tls_session.tls13_derived_secrets['server_traffic_secrets']) == 1) +assert(t.tls_session.tls13_derived_secrets['server_traffic_secrets'][0] == server_application_traffic_secret_0) + +assert('exporter_secret' in t.tls_session.tls13_derived_secrets) +assert(t.tls_session.tls13_derived_secrets['exporter_secret'] == exporter_master_secret) + +assert('resumption_secret' in t.tls_session.tls13_derived_secrets) +assert(t.tls_session.tls13_derived_secrets['resumption_secret'] == resumption_master_secret) + + += Decrypt a resumed TLS 1.3 session with 0-RTT - Decrypt and parse client application data + +# Values from RFC8448, section 4 +clientAppData = clean(""" + 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e + 0f 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 21 22 23 + 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 31 + """) + +clientEncAppData = clean(""" + 17 03 03 00 43 b1 ce bc e2 42 aa 20 + 1b e9 ae 5e 1c b2 a9 aa 4b 33 d4 e8 66 af 1e db 06 89 19 23 77 + 41 aa 03 1d 7a 74 d4 91 c9 9b 9d 4e 23 2b 74 20 6b c6 fb aa 04 + fe 78 be 44 a9 b4 f5 43 20 a1 7e b7 69 92 af ac 31 03 + """) + +t = TLS13(clientEncAppData, tls_session = t.tls_session) +assert(t.deciphered_len == 51) +assert(len(t.inner.msg) == 1) +assert(isinstance(t.inner.msg[0],TLSApplicationData)) +assert(t.inner.msg[0].data == clientAppData) +assert(t.inner.type == 23) + += Decrypt a resumed TLS 1.3 session with 0-RTT - Decrypt and parse server application data + +# Values from RFC8448, section 4 +serverAppData = clean(""" + 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e + 0f 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 21 22 23 + 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 31 + """) + +serverEncAppData = clean(""" + 17 03 03 00 43 27 5e 9f 20 ac ff 57 + bc 00 06 57 d3 86 7d f0 39 cc cf 79 04 78 84 cf 75 77 17 46 f7 + 40 b5 a8 3f 46 2a 09 54 c3 58 13 93 a2 03 a2 5a 7d d1 41 41 ef + 1a 37 90 0c db 62 ff 62 de e1 ba 39 ab 25 90 cb f1 94 + """) +t = TLS13(serverEncAppData, tls_session = t.tls_session.mirror()) +assert(t.deciphered_len == 51) +assert(len(t.inner.msg) == 1) +assert(isinstance(t.inner.msg[0],TLSApplicationData)) +assert(t.inner.msg[0].data == serverAppData) +assert(t.inner.type == 23) + += Decrypt a resumed TLS 1.3 session with 0-RTT - Decrypt and parse client Alert + +# Values from RFC8448, section 4 +clientEncAlert = clean(""" + 17 03 03 00 13 0f ac ce 32 46 bd fc + 63 69 83 8d 6a 82 ae 6d e5 d4 22 dc + """) + +t = TLS13(clientEncAlert, tls_session = t.tls_session.mirror()) +assert(t.deciphered_len == 3) +assert(len(t.inner.msg) == 1) +assert(t.inner.type == 21) +m = t.inner.msg[0] +assert(isinstance(m, TLSAlert)) +assert(m.level == 1) +assert(m.descr == 0) + + += Decrypt a resumed TLS 1.3 session with 0-RTT - Decrypt and parse server Alert + +# Values from RFC8448, section 4 +serverEncAlert = clean(""" + 17 03 03 00 13 5b 18 af 44 4e 8e 1e + ec 71 58 fb 62 d8 f2 57 7d 37 ba 5d + """) + +t = TLS13(serverEncAlert, tls_session = t.tls_session.mirror()) +assert(t.deciphered_len == 3) +assert(len(t.inner.msg) == 1) +assert(t.inner.type == 21) +m = t.inner.msg[0] +assert(isinstance(m, TLSAlert)) +assert(m.level == 1) +assert(m.descr == 0) + + + + + +########### HelloRetryRequest ############################################### ++ Decrypt a TLS 1.3 session with a retry + += Decrypt a TLS 1.3 session with a retry - Parse first ClientHello +# Values from RFC8448, section 5 +x25519_clt_priv = clean(""" + 0e d0 2f 8e 81 17 ef c7 5c a7 ac 32 aa + 7e 34 ed a6 4c dc 0d da d1 54 a5 e8 52 89 f9 59 f6 32 04 + """) + +x25519_clt_pub = clean(""" + e8 e8 e3 f3 b9 3a 25 ed 97 a1 4a 7d ca cb + 8a 27 2c 62 88 e5 85 c6 48 4d 05 26 2f ca d0 62 ad 1f + """) + +clientHello1 = clean(""" + 16 03 01 00 b4 01 00 00 b0 03 03 b0 + b1 c5 a5 aa 37 c5 91 9f 2e d1 d5 c6 ff f7 fc b7 84 97 16 94 5a + 2b 8c ee 92 58 a3 46 67 7b 6f 00 00 06 13 01 13 03 13 02 01 00 + 00 81 00 00 00 0b 00 09 00 00 06 73 65 72 76 65 72 ff 01 00 01 + 00 00 0a 00 08 00 06 00 1d 00 17 00 18 00 33 00 26 00 24 00 1d + 00 20 e8 e8 e3 f3 b9 3a 25 ed 97 a1 4a 7d ca cb 8a 27 2c 62 88 + e5 85 c6 48 4d 05 26 2f ca d0 62 ad 1f 00 2b 00 03 02 03 04 00 + 0d 00 20 00 1e 04 03 05 03 06 03 02 03 08 04 08 05 08 06 04 01 + 05 01 06 01 02 01 04 02 05 02 06 02 02 02 00 2d 00 02 01 01 00 + 1c 00 02 40 01 + """) + t = TLS(clientHello1) +assert(len(t.msg) == 1) +assert(t.msg[0].msgtype == 1) +assert(t.msg[0].extlen == 129) +assert(len(t.msg[0].ext) == 8) +e = t.msg[0].ext +assert(isinstance(e[0], TLS_Ext_ServerName)) +assert(isinstance(e[1], TLS_Ext_RenegotiationInfo)) +assert(isinstance(e[2], TLS_Ext_SupportedGroups)) +assert(isinstance(e[3],TLS_Ext_KeyShare_CH)) +assert(len(e[3].client_shares) == 1) +assert(e[3].client_shares[0].group == 29) +assert(e[3].client_shares[0].key_exchange == x25519_clt_pub) +assert(isinstance(e[4], TLS_Ext_SupportedVersion_CH)) +assert(isinstance(e[5], TLS_Ext_SignatureAlgorithms)) +assert(isinstance(e[6], TLS_Ext_PSKKeyExchangeModes)) +assert(e[6].kxmodeslen == 1) +assert(len(e[6].kxmodes) == 1) +assert(e[6].kxmodes[0] == 1) +assert(isinstance(e[7], TLS_Ext_RecordSizeLimit)) + + +secp256_srv_pub = clean(""" + 8c 51 06 01 f9 76 5b fb 8e d6 93 44 9a + 48 98 98 59 b5 cf a8 79 cb 9f 54 43 c4 1c 5f f1 06 34 ed + """) + +secp256_srv_pub = clean(""" + 04 58 3e 05 4b 7a 66 67 2a e0 20 ad 9d 26 + 86 fc c8 5b 5a d4 1a 13 4a 0f 03 ee 72 b8 93 05 2b d8 5b 4c 8d + e6 77 6f 5b 04 ac 07 d8 35 40 ea b3 e3 d9 c5 47 bc 65 28 c4 31 + 7d 29 46 86 09 3a 6c ad 7d + """) + + + += Decrypt a TLS 1.3 session with a retry - Parse ServerHelloRetryRequest +from scapy.layers.tls.keyexchange_tls13 import TLS_Ext_KeyShare_HRR +from scapy.layers.tls.extensions import TLS_Ext_Cookie +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import hashes +# Value from RFC8448, section 5 helloRetryRequest = clean(""" - 160301000e060000 0a7f120006002800 020017 - """) + 16 03 03 00 b0 02 00 00 ac 03 03 cf + 21 ad 74 e5 9a 61 11 be 1d 8c 02 1e 65 b8 91 c2 a2 11 16 7a bb + 8c 5e 07 9e 09 e2 c8 a8 33 9c 00 13 01 00 00 84 00 33 00 02 00 + 17 00 2c 00 74 00 72 71 dc d0 4b b8 8b c3 18 91 19 39 8a 00 00 + 00 00 ee fa fc 76 c1 46 b8 23 b0 96 f8 aa ca d3 65 dd 00 30 95 + 3f 4e df 62 56 36 e5 f2 1b b2 e2 3f cc 65 4b 1b 5b 40 31 8d 10 + d1 37 ab cb b8 75 74 e3 6e 8a 1f 02 5f 7d fa 5d 6e 50 78 1b 5e + da 4a a1 5b 0c 8b e7 78 25 7d 16 aa 30 30 e9 e7 84 1d d9 e4 c0 + 34 22 67 e8 ca 0c af 57 1f b2 b7 cf f0 f9 34 b0 00 2b 00 02 03 + 04 + """) + t = TLS(helloRetryRequest, tls_session=t.tls_session.mirror()) +assert(len(t.msg) == 1) +assert(t.msg[0].msgtype == 2) +digest = hashes.Hash(hashes.SHA256(), backend=default_backend()) +digest.update(b"HelloRetryRequest") +assert(t.msg[0].random_bytes == digest.finalize()) +assert(t.msg[0].extlen == 132) +assert(len(t.msg[0].ext) == 3) +e = t.msg[0].ext +assert(isinstance(e[0], TLS_Ext_KeyShare_HRR)) +assert(e[0].type == 51) +assert(e[0].len == 2) +assert(e[0].selected_group == 23) +assert(isinstance(e[1], TLS_Ext_Cookie)) +assert(e[1].type == 44) +assert(e[1].len == 116) +assert(e[1].cookielen == 114) +assert(e[1].cookie == b'q\xdc\xd0K\xb8\x8b\xc3\x18\x91\x199\x8a\x00\x00\x00\x00\xee\xfa\xfcv\xc1F\xb8#\xb0\x96\xf8\xaa\xca\xd3e\xdd\x000\x95?N\xdfbV6\xe5\xf2\x1b\xb2\xe2?\xcceK\x1b[@1\x8d\x10\xd17\xab\xcb\xb8ut\xe3n\x8a\x1f\x02_}\xfa]nPx\x1b^\xdaJ\xa1[\x0c\x8b\xe7x%}\x16\xaa00\xe9\xe7\x84\x1d\xd9\xe4\xc04"g\xe8\xca\x0c\xafW\x1f\xb2\xb7\xcf\xf0\xf94\xb0') +assert(isinstance(e[2], TLS_Ext_SupportedVersion_SH)) + + += Decrypt a TLS 1.3 session with a retry - Parse second ClientHello +# Values from RFC8448, section 5 +secp256_clt_pub = clean(""" + 04 a6 da 73 92 ec 59 1e 17 ab fd 53 59 64 + b9 98 94 d1 3b ef b2 21 b3 de f2 eb e3 83 0e ac 8f 01 51 81 26 + 77 c4 d6 d2 23 7e 85 cf 01 d6 91 0c fb 83 95 4e 76 ba 73 52 83 + 05 34 15 98 97 e8 06 57 80 + """) + +secp256_clt_priv = clean(""" + ab 54 73 46 7e 19 34 6c eb 0a 04 14 e4 + 1d a2 1d 4d 24 45 bc 30 25 af e9 7c 4e 8d c8 d5 13 da 39 + """) -secp256r1_client_privkey = clean(""" - 11fa48d153c917ff d89dff13140760a1 - 36265d399fa9f10e 2d766d42a6c84e90 - """) clientHello2 = clean(""" - 16030100cf010000 cb0303d9e9898df6 - 3d43adbe64a2634f 0b63bcdc4019a3e5 26bc013a6042e05b - 14555c0000061301 130313020100009c 0000000b00090000 - 06736572766572ff 01000100000a0008 0006001d00170018 - 0028004700450017 0041041e5a785f54 17fb18db42938435 - 34a5c0ba6e744baa 6846d0b32f4e9ea3 922724a08f2adb09 - f071f81402e7fd8c a33b76abe1cd556f d3e8fe20e0fd2e82 - 02f969002b000302 7f12000d0020001e 0403050306030203 - 0804080508060401 0501060102010402 050206020202002d 00020101 - """) + 16 03 03 02 00 01 00 01 fc 03 03 b0 + b1 c5 a5 aa 37 c5 91 9f 2e d1 d5 c6 ff f7 fc b7 84 97 16 94 5a + 2b 8c ee 92 58 a3 46 67 7b 6f 00 00 06 13 01 13 03 13 02 01 00 + 01 cd 00 00 00 0b 00 09 00 00 06 73 65 72 76 65 72 ff 01 00 01 + 00 00 0a 00 08 00 06 00 1d 00 17 00 18 00 33 00 47 00 45 00 17 + 00 41 04 a6 da 73 92 ec 59 1e 17 ab fd 53 59 64 b9 98 94 d1 3b + ef b2 21 b3 de f2 eb e3 83 0e ac 8f 01 51 81 26 77 c4 d6 d2 23 + 7e 85 cf 01 d6 91 0c fb 83 95 4e 76 ba 73 52 83 05 34 15 98 97 + e8 06 57 80 00 2b 00 03 02 03 04 00 0d 00 20 00 1e 04 03 05 03 + 06 03 02 03 08 04 08 05 08 06 04 01 05 01 06 01 02 01 04 02 05 + 02 06 02 02 02 00 2c 00 74 00 72 71 dc d0 4b b8 8b c3 18 91 19 + 39 8a 00 00 00 00 ee fa fc 76 c1 46 b8 23 b0 96 f8 aa ca d3 65 + dd 00 30 95 3f 4e df 62 56 36 e5 f2 1b b2 e2 3f cc 65 4b 1b 5b + 40 31 8d 10 d1 37 ab cb b8 75 74 e3 6e 8a 1f 02 5f 7d fa 5d 6e + 50 78 1b 5e da 4a a1 5b 0c 8b e7 78 25 7d 16 aa 30 30 e9 e7 84 + 1d d9 e4 c0 34 22 67 e8 ca 0c af 57 1f b2 b7 cf f0 f9 34 b0 00 + 2d 00 02 01 01 00 1c 00 02 40 01 00 15 00 af 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 + """) + t = TLS(clientHello2, tls_session=t.tls_session.mirror()) +assert(len(t.msg) == 1) +assert(t.msg[0].msgtype == 1) +assert(t.msg[0].extlen == 461) +assert(len(t.msg[0].ext) == 10) +e = t.msg[0].ext +assert(isinstance(e[0], TLS_Ext_ServerName)) +assert(isinstance(e[1], TLS_Ext_RenegotiationInfo)) +assert(isinstance(e[2], TLS_Ext_SupportedGroups)) +assert(isinstance(e[3],TLS_Ext_KeyShare_CH)) +assert(len(e[3].client_shares) == 1) +assert(e[3].client_shares[0].group == 23) +assert(e[3].client_shares[0].key_exchange == secp256_clt_pub) +assert(isinstance(e[4], TLS_Ext_SupportedVersion_CH)) +assert(isinstance(e[5], TLS_Ext_SignatureAlgorithms)) +assert(isinstance(e[6], TLS_Ext_Cookie)) +assert(e[6].cookie == b'q\xdc\xd0K\xb8\x8b\xc3\x18\x91\x199\x8a\x00\x00\x00\x00\xee\xfa\xfcv\xc1F\xb8#\xb0\x96\xf8\xaa\xca\xd3e\xdd\x000\x95?N\xdfbV6\xe5\xf2\x1b\xb2\xe2?\xcceK\x1b[@1\x8d\x10\xd17\xab\xcb\xb8ut\xe3n\x8a\x1f\x02_}\xfa]nPx\x1b^\xdaJ\xa1[\x0c\x8b\xe7x%}\x16\xaa00\xe9\xe7\x84\x1d\xd9\xe4\xc04"g\xe8\xca\x0c\xafW\x1f\xb2\xb7\xcf\xf0\xf94\xb0') +assert(isinstance(e[7], TLS_Ext_PSKKeyExchangeModes)) +assert(e[7].kxmodeslen == 1) +assert(len(e[7].kxmodes) == 1) +assert(e[7].kxmodes[0] == 1) +assert(isinstance(e[8], TLS_Ext_RecordSizeLimit)) +assert(isinstance(e[9], TLS_Ext_Padding)) +assert(e[9].len == 175) +assert(e[9].padding == 175*b'\x00') + += Decrypt a TLS 1.3 session with a retry - Parse ServerHello +from cryptography.hazmat.primitives.asymmetric.ec import EllipticCurvePrivateNumbers pubnum = t.tls_session.tls13_client_pubshares["secp256r1"].public_numbers() -privnum = EllipticCurvePrivateNumbers(pkcs_os2ip(secp256r1_client_privkey), pubnum) +privnum = EllipticCurvePrivateNumbers(pkcs_os2ip(secp256_clt_priv), pubnum) privkey = privnum.private_key(default_backend()) t.tls_session.tls13_client_privshares["secp256r1"] = privkey -#secp256r1_server_privkey = clean(""" -# ff265d2062c70725 ca22513e1e6841ff -# 475e8a00421f0818 186edd1c0080cc6a -# """) serverHello = clean(""" - 1603010073020000 6f7f1296ff693075 - d8465651a9c28773 f5496542206ba390 199b9c997545d9a1 - 2666151301004900 2800450017004104 8a4d09cde58dbc04 - 1955b9a41a43c169 6dc5429ffa96f9cd 194a863ac782f181 - 59f072b4f610215d 86407dd7368b754a b2e64f2c1b3f9d45 - 7c264e2b1781a36b - """) + 16 03 03 00 7b 02 00 00 77 03 03 bb + 34 1d 84 7f d7 89 c4 7c 38 71 72 dc 0c 9b f1 47 fc ca cb 50 43 + d8 6c a4 c5 98 d3 ff 57 1b 98 00 13 01 00 00 4f 00 33 00 45 00 + 17 00 41 04 58 3e 05 4b 7a 66 67 2a e0 20 ad 9d 26 86 fc c8 5b + 5a d4 1a 13 4a 0f 03 ee 72 b8 93 05 2b d8 5b 4c 8d e6 77 6f 5b + 04 ac 07 d8 35 40 ea b3 e3 d9 c5 47 bc 65 28 c4 31 7d 29 46 86 + 09 3a 6c ad 7d 00 2b 00 02 03 04 + """) + t = TLS(serverHello, tls_session=t.tls_session.mirror()) +assert(len(t.msg) == 1) +assert(isinstance(t.msg[0], TLS13ServerHello)) +assert(len(t.msg[0].ext) == 2) +e = t.msg[0].ext +assert(isinstance(e[0], TLS_Ext_KeyShare_SH)) +assert(e[0].server_share.group == 23) +assert(e[0].server_share.key_exchange == secp256_srv_pub) +assert(isinstance(e[1], TLS_Ext_SupportedVersion_SH)) + += Decrypt a TLS 1.3 session with a retry - Handshake traffic secret derivation + +# Values from RFC8448, section 5 +early_secret = clean(""" + 33 ad 0a 1c 60 7e c0 3b 09 e6 cd 98 93 68 0c + e2 10 ad f3 00 aa 1f 26 60 e1 b2 2e 10 f1 70 f9 2a + """) + +ecdhe_secret = clean(""" + c1 42 ce 13 ca 11 b5 c2 23 36 52 e6 3a d3 d9 78 + 44 f1 62 1f bf b9 de 69 d5 47 dc 8f ed ea be b4 + """) + +handshake_secret = clean(""" + ce 02 2e 5e 6e 81 e5 07 36 d7 73 f2 d3 ad fc + e8 22 0d 04 9b f5 10 f0 db fa c9 27 ef 42 43 b1 48 + """) + +client_handshake_traffic_secret = clean(""" + 15 8a a7 ab 88 55 07 35 82 b4 1d 67 4b 40 + 55 ca bc c5 34 72 8f 65 93 14 86 1b 4e 08 e2 01 15 66 + """) + +server_handshake_traffic_secret = clean(""" + 34 03 e7 81 e2 af 7b 65 08 da 28 57 4f 6e + 95 a1 ab f1 62 de 83 a9 79 27 c3 76 72 a4 a0 ce f8 a1 + """) + +assert(len(t.tls_session.tls13_derived_secrets) == 5) +assert(t.tls_session.tls13_early_secret is not None) +assert(t.tls_session.tls13_early_secret == early_secret) +assert(t.tls_session.tls13_dhe_secret == ecdhe_secret) +assert(t.tls_session.tls13_handshake_secret is not None) +assert(t.tls_session.tls13_handshake_secret == handshake_secret) +assert( 'client_handshake_traffic_secret' in t.tls_session.tls13_derived_secrets) +assert( t.tls_session.tls13_derived_secrets['client_handshake_traffic_secret'] == client_handshake_traffic_secret) +assert( 'server_handshake_traffic_secret' in t.tls_session.tls13_derived_secrets) +assert(t.tls_session.tls13_derived_secrets['server_handshake_traffic_secret'] == server_handshake_traffic_secret) + + += Decrypt a TLS 1.3 session with a retry - Server handshake traffic key calculation +# Values from RFC8448, section 5 +server_hs_traffic_key = clean(""" + 46 46 bf ac 17 12 c4 26 cd 78 d8 a2 4a + 8a 6f 6b + """) +server_hs_traffic_iv = clean(""" + c7 d3 95 c0 8d 62 f2 97 d1 37 68 ea + """) +assert(t.tls_session.prcs.cipher.key == server_hs_traffic_key) +assert(t.tls_session.prcs.cipher.fixed_iv == server_hs_traffic_iv) + + += Decrypt a TLS 1.3 session with a retry - Decrypt and parse server handshake +# Values from RFC8448, section 5 serverEncHS = clean(""" - 170301029081de4f cfd700da4573d570 - 5942f14a11e569aa 9aacc95260520102 6f74f2b2ad6abe08 - 7b53a4940ff94208 9e02d3159b1c6f11 75d7fcb51abad6fd - d4f7ff4af6590b47 16c1d90e1031e1a1 e32079f531108c6b - 9f79d6120319e0a3 73010e82d780a8f9 c3fdf8474840cdb6 - 7e4943d3808a27cd 5d9375c766a95ef4 8393c235d83ad26a - 20628671793f75df aa0be78b11fed206 6506d19a769d9d32 - adc0437784994359 ef5e452609353670 1c46004cf6fc252e - 546e797238c73b94 b073461158301f78 1498917c32dc0ece - 658a53790c667397 f7744775c2bef907 b5f7d5677b2e57fe - 7c4bfd43c7ad1ee4 6fd400c3d3c3c05f e8775f055263e98a - 692b49a818d0f698 4400c1db2f429fa8 9fb61d523398e1d0 - 2bc5c393027146c0 f326032d18cb8283 473f2b6d554df942 - c7b1a0050694c7b2 bf31a816f7ff77f1 d7db873dbb6e4646 - acabfa73c317a34c e6212a3469f549e6 cde71ab229a6f220 - acda60832b510663 02a23d02c734bd5e 71b04fb248ca47ba - 0c7b1fd28fee9b5d 86e6b1a6a2a1a43e 3831210519f54134 - c96486d11ef3125f 74969785690487e0 aa5c0a310ebf9d31 - 95ec5543af8a6ffb 710eb0a90285960d c1ccdc10ecee9669 - 9171e97eae526a17 205012ab6f262e44 31ae9a70ff2ed7bd - 966ef6bd4563f56a 7a14970dcabf97ae 7e4354db1ea27548 - c55c11542ad07bcd 6f47a7143b86c4e6 678ce7dc6d51a1b7 - 75687644d6526efa 3c864f592819e7b7 f9f1bbc02ed8821a - e66019b240b41f5e ebf9475069700030 7122f7c8a8d6c0da - a264c63183238d72 0eacb86879fab9ba 8a673c51a52c8284 - 75e3211223cd2238 bd8b8a934af3e4dd e10e788df23ad6d8 - 51d68b78082ac667 a854356415e7858b e526307332990d8c - c38a5dc4cfc22a2c a2bdd9126a2ce13d 7015264921 - """) -t = TLS(serverEncHS, tls_session=t.tls_session) + 17 03 03 02 96 99 be e2 0b af 5b 7f + c7 27 bf ab 62 23 92 8a 38 1e 6d 0c f9 c4 da 65 3f 9d 2a 7b 23 + f7 de 11 cc e8 42 d5 cf 75 63 17 63 45 0f fb 8b 0c c1 d2 38 e6 + 58 af 7a 12 ad c8 62 43 11 4a b1 4a 1d a2 fa e4 26 21 ce 48 3f + b6 24 2e ab fa ad 52 56 6b 02 b3 1d 2e dd ed ef eb 80 e6 6a 99 + 00 d5 f9 73 b4 0c 4f df 74 71 9e cf 1b 68 d7 f9 c3 b6 ce b9 03 + ca 13 dd 1b b8 f8 18 7a e3 34 17 e1 d1 52 52 2c 58 22 a1 a0 3a + d5 2c 83 8c 55 95 3d 61 02 22 87 4c ce 8e 17 90 b2 29 a2 aa 0b + 53 c8 d3 77 ee 72 01 82 95 1d c6 18 1d c5 d9 0b d1 f0 10 5e d1 + e8 4a a5 f7 59 57 c6 66 18 97 07 9e 5e a5 00 74 49 e3 19 7b dc + 7c 9b ee ed dd ea fd d8 44 af a5 c3 15 ec fe 65 e5 76 af e9 09 + 81 28 80 62 0e c7 04 8b 42 d7 f5 c7 8d 76 f2 99 d6 d8 25 34 bd + d8 f5 12 fe bc 0e d3 81 4a ca 47 0c d8 00 0d 3e 1c b9 96 2b 05 + 2f bb 95 0d f6 83 a5 2c 2b a7 7e d3 71 3b 12 29 37 a6 e5 17 09 + 64 e2 ab 79 69 dc d9 80 b3 db 9b 45 8d a7 60 31 24 d6 dc 00 5e + 4d 6e 04 b4 d0 c4 ba f3 27 5d b8 27 db ba 0a 6d b0 96 72 17 1f + c0 57 b3 85 1d 7e 02 68 41 e2 97 8f bd 23 46 bb ef dd 03 76 bb + 11 08 fe 9a cc 92 18 9f 56 50 aa 5e 85 d8 e8 c7 b6 7a c5 10 db + a0 03 d3 d7 e1 63 50 bb 66 d4 50 13 ef d4 4c 9b 60 7c 0d 31 8c + 4c 7d 1a 1f 5c bc 57 e2 06 11 80 4e 37 87 d7 b4 a4 b5 f0 8e d8 + fd 70 bd ae ad e0 22 60 b1 2a b8 42 ef 69 0b 4a 3e e7 91 1e 84 + 1b 37 4e cd 5e bb bc 2a 54 d0 47 b6 00 33 6d d7 d0 c8 8b 4b c1 + 0e 58 ee 6c b6 56 de 72 47 fa 20 d8 e9 1d eb 84 62 86 08 cf 80 + 61 5b 62 e9 6c 14 91 c7 ac 37 55 eb 69 01 40 5d 34 74 fe 1a c7 + 9d 10 6a 0c ee 56 c2 57 7f c8 84 80 f9 6c b6 b8 c6 81 b7 b6 8b + 53 c1 46 09 39 08 f3 50 88 81 75 bd fb 0b 1e 31 ad 61 e3 0b a0 + ad fe 6d 22 3a a0 3c 07 83 b5 00 1a 57 58 7c 32 8a 9a fc fc fb + 97 8d 1c d4 32 8f 7d 9d 60 53 0e 63 0b ef d9 6c 0c 81 6e e2 0b + 01 00 76 8a e2 a6 df 51 fc 68 f1 72 74 0a 79 af 11 39 8e e3 be + 12 52 49 1f a9 c6 93 47 9e 87 7f 94 ab 7c 5f 8c ad 48 02 03 e6 + ab 7b 87 dd 71 e8 a0 72 91 13 df 17 f5 ee e8 6c e1 08 d1 d7 20 + 07 ec 1c d1 3c 85 a6 c1 49 62 1e 77 b7 d7 8d 80 5a 30 f0 be 03 + 0c 31 5e 54 + """) + +server_finished = clean(""" + 88 63 e6 bf b0 42 0a 92 7f a2 7f 34 33 6a + 70 ae 42 6e 96 8e 3e b8 84 94 5b 96 85 6d ba 39 76 d1 + """) + +t = TLS13(serverEncHS, tls_session=t.tls_session) +assert(t.deciphered_len == 646) +assert(len(t.inner.msg) == 4) +m = t.inner.msg +assert(isinstance(m[0], TLSEncryptedExtensions)) +assert(len(m[0].ext) == 3) +assert(isinstance(m[0].ext[0], TLS_Ext_SupportedGroups)) +assert(isinstance(m[0].ext[1], TLS_Ext_RecordSizeLimit)) +assert(isinstance(m[0].ext[2], TLS_Ext_ServerName)) +assert(isinstance(m[1], TLS13Certificate)) +assert(isinstance(m[2], TLSCertificateVerify)) +assert(isinstance(m[3], TLSFinished)) +assert(m[3].vdata == server_finished) + += Decrypt a TLS 1.3 session with a retry - Client handshake traffic key calculation +# Values from RFC8448, section 5 +client_hs_traffic_key = clean(""" + 2f 1f 91 86 63 d5 90 e7 42 11 49 a2 9d + 94 b0 b6 + """) +client_hs_traffic_iv = clean(""" + 41 4d 54 85 23 5e 1a 68 87 93 bd 74 + """) + +assert(t.tls_session.pwcs.cipher.key == client_hs_traffic_key) +assert(t.tls_session.pwcs.cipher.fixed_iv == client_hs_traffic_iv) + += Decrypt a TLS 1.3 session with a retry - Decrypt and parse client finished +# Values from RFC8448, section 5 clientFinished = clean(""" - 170301003543adad e592362412fb77d7 - 28b181c01b77cd62 a661e4125e6f9851 826e418f4c292ec6 - 3254e8b0342d65db 8a7f074eed527ea6 98a6 - """) -t = TLS(clientFinished, tls_session=t.tls_session.mirror()) + 23 f5 2f db 07 09 a5 5b d7 f7 9b 99 1f 25 + 48 40 87 bc fd 4d 43 80 b1 23 26 a5 2a 28 b2 e3 68 e1 + """) -clientRecord = clean(""" - 17030100131ef5c9 e7205f31a1edf9b1 - 3600fec1271e4f5d - """) -t = TLS(clientRecord, tls_session=t.tls_session) +clientEncHS = clean(""" + 17 03 03 00 35 d7 4f 19 23 c6 62 fd + 34 13 7c 6f 50 2f 3d d2 b9 3d 95 1d 1b 3b c9 7e 42 af e2 3c 31 + ab ea 92 fe 91 b4 74 99 9e 85 e3 b7 91 ce 25 2f e8 c3 e9 f9 39 + a4 12 0c b2 + """) -serverRecord = clean(""" - 170301001350ff6e 907c508b6b191ff6 - 094faf4c0b32d6a8 - """) -t = TLS(serverRecord, tls_session=t.tls_session.mirror()) +t = TLS13(clientEncHS, tls_session=t.tls_session.mirror()) +assert(t.deciphered_len == 37) +assert(len(t.inner.msg) == 1) +assert(isinstance(t.inner.msg[0], TLSFinished)) +assert(t.inner.msg[0].vdata == clientFinished) +assert(t.inner.type == 22) -alert = t.inner.msg[0] -assert(isinstance(alert, TLSAlert)) -alert.level == 1 and alert.descr == 0 += Decrypt a TLS 1.3 session with a retry - Application traffic secret derivation +# Values from RFC8448, section 5 +master_secret = clean(""" + 11 31 54 5d 0b af 79 dd ce 9b 87 f0 69 45 78 + 1a 57 dd 18 ef 37 8d cd 20 60 f8 f9 a5 69 02 7e d8 + """) +client_application_traffic_secret_0 = clean(""" + 75 ec f4 b9 72 52 5a a0 dc d0 57 c9 94 4d + 4c d5 d8 26 71 d8 84 31 41 d7 dc 2a 4f f1 5a 21 dc 51 + """) -= Reading TLS 1.3 test session (vectors 3 from draft-ietf-tls-tls13-vectors-00) -~ crypto_advanced -from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey +server_application_traffic_secret_0 = clean(""" + 5c 74 f8 7d f0 42 25 db 0f 82 09 c9 de 64 + 29 e4 94 35 fd ef a7 ca d6 18 64 87 4d 12 f3 1c fc 8d + """) + +exporter_master_secret = clean(""" + 7c 06 d3 ae 10 6a 3a 37 4a ce 48 37 b3 98 + 5c ac 67 78 0a 6e 2c 5c 04 b5 83 19 d5 84 df 09 d2 23 + """) + +resumption_master_secret = clean(""" + 09 17 0c 6d 47 27 21 56 6f 9c f9 9b 08 69 + 9d af f5 61 ec 8f b2 2d 5a 32 c3 f9 4c e0 09 b6 99 75 + """) + + +assert(t.tls_session.tls13_master_secret is not None) +assert(t.tls_session.tls13_master_secret == master_secret) + +assert(len(t.tls_session.tls13_derived_secrets) == 9) +assert('client_traffic_secrets' in t.tls_session.tls13_derived_secrets) +assert(len(t.tls_session.tls13_derived_secrets['client_traffic_secrets']) == 1) +assert(t.tls_session.tls13_derived_secrets['client_traffic_secrets'][0] == client_application_traffic_secret_0) + +assert('server_traffic_secrets' in t.tls_session.tls13_derived_secrets) +assert(len(t.tls_session.tls13_derived_secrets['server_traffic_secrets']) == 1) +assert(t.tls_session.tls13_derived_secrets['server_traffic_secrets'][0] == server_application_traffic_secret_0) + +assert('exporter_secret' in t.tls_session.tls13_derived_secrets) +assert(t.tls_session.tls13_derived_secrets['exporter_secret'] == exporter_master_secret) + +assert('resumption_secret' in t.tls_session.tls13_derived_secrets) +assert(t.tls_session.tls13_derived_secrets['resumption_secret'] == resumption_master_secret) + += Decrypt a TLS 1.3 session with a retry - Application traffic keys calculation +# Values from RFC8448, section 5 +client_ap_traffic_key = clean(""" + a7 eb 2a 05 25 eb 43 31 d5 8f cb f9 f7 + ca 2e 9c + """) + +client_ap_traffic_iv = clean(""" + 86 e8 be 22 7c 1b d2 b3 e3 9c b4 44 + """) + +server_ap_traffic_key = clean(""" + f2 7a 5d 97 bd 25 55 0c 48 23 b0 f3 e5 + d2 93 88 + """) + +server_ap_traffic_iv = clean(""" + 0d d6 31 f7 b7 1c bb c7 97 c3 5f e7 + """) + +# wcs instead of pwcs ? +assert(t.tls_session.rcs.cipher.key == client_ap_traffic_key) +assert(t.tls_session.rcs.cipher.fixed_iv == client_ap_traffic_iv) +assert(t.tls_session.wcs.cipher.key == server_ap_traffic_key) +assert(t.tls_session.wcs.cipher.fixed_iv == server_ap_traffic_iv) + += Decrypt a TLS 1.3 session with a retry - Decrypt and parse client Alert +# Values from RFC8448, section 5 +clientEncAlert = clean(""" + 17 03 03 00 13 2e a6 cd f7 49 19 60 + 23 e2 b3 a4 94 91 69 55 36 42 60 47 + """) + +t = TLS13(clientEncAlert, tls_session = t.tls_session) +assert(t.deciphered_len == 3) +assert(len(t.inner.msg) == 1) +assert(t.inner.type == 21) +m = t.inner.msg[0] +assert(isinstance(m, TLSAlert)) +assert(m.level == 1) +assert(m.descr == 0) + + += Decrypt a TLS 1.3 session with a retry - Decrypt and parse server Alert +# Values from RFC8448, section 5 +serverEncAlert = clean(""" + 17 03 03 00 13 51 9f c5 07 5c b0 88 + 43 49 75 9f f9 ef 6f 01 1b b4 c6 f2 + """) + +t = TLS13(serverEncAlert, tls_session = t.tls_session.mirror()) +assert(t.deciphered_len == 3) +assert(len(t.inner.msg) == 1) +assert(t.inner.type == 21) +m = t.inner.msg[0] +assert(isinstance(m, TLSAlert)) +assert(m.level == 1) +assert(m.descr == 0) + + +########### Client Authentication ############################################### ++ Decrypt a TLS 1.3 session with client authentication + += Decrypt a TLS 1.3 session with client authentication - Parse client Hello +# Values from RFC8448, section 6 +x25519_clt_priv = clean(""" + c0 40 b2 bb 8f 3a dd d2 0f d4 05 8c 54 + 70 03 a3 c6 f9 c1 cd 91 5d 5e 53 5c 87 d8 d1 91 aa f0 71 + """) + +x25519_clt_pub = clean(""" + 08 9c c2 67 1f 73 8d 9a 67 1e 5b 2e 46 49 + 81 d0 5b 76 e3 61 aa 22 ae a9 1f 1d 49 ca 10 a7 a3 62 + """) -x25519_client_privkey = clean(""" - 00b4198a84ed6a7c 218702891735239d - 40b7c66505330364 3d3c67f7458ecbc9 - """) clientHello = clean(""" - 1603010200010001 fc03039a464db650 - dcc81fed6f1fea63 5f15861574c0ed0b fb5778de7724fb92 - 7c5ef100003e1301 13031302c02bc02f cca9cca8c00ac009 - c013c023c027c014 009eccaa00330032 006700390038006b - 00160013009c002f 003c0035003d000a 0005000401000195 - 001500fc00000000 0000000000000000 0000000000000000 - 0000000000000000 0000000000000000 0000000000000000 - 0000000000000000 0000000000000000 0000000000000000 - 0000000000000000 0000000000000000 0000000000000000 - 0000000000000000 0000000000000000 0000000000000000 - 0000000000000000 0000000000000000 0000000000000000 - 0000000000000000 0000000000000000 0000000000000000 - 0000000000000000 0000000000000000 0000000000000000 - 0000000000000000 0000000000000000 0000000000000000 - 0000000000000000 0000000000000000 0000000000000000 - 0000000000000000 0000000000000000 0000000b00090000 - 06736572766572ff 01000100000a0014 0012001d00170018 - 0019010001010102 01030104000b0002 0100002300000028 - 00260024001d0020 35e58b160db6124f 01a1d2475a22b72a - bd6896701eed4c7e fd6124ee231ba458 002b0007067f1203 - 030302000d002000 1e04030503060302 0308040805080604 - 0105010601020104 0205020602020200 2d00020101 - """) + 16 03 01 00 c0 01 00 00 bc 03 03 6a + 47 22 36 32 8b 83 af 40 38 6d 3a 3e 1f 1c e6 24 fa 4e d8 9a b8 + 65 a4 ff 0f 41 44 ce 3a e2 33 00 00 06 13 01 13 03 13 02 01 00 + 00 8d 00 00 00 0b 00 09 00 00 06 73 65 72 76 65 72 ff 01 00 01 + 00 00 0a 00 14 00 12 00 1d 00 17 00 18 00 19 01 00 01 01 01 02 + 01 03 01 04 00 33 00 26 00 24 00 1d 00 20 08 9c c2 67 1f 73 8d + 9a 67 1e 5b 2e 46 49 81 d0 5b 76 e3 61 aa 22 ae a9 1f 1d 49 ca + 10 a7 a3 62 00 2b 00 03 02 03 04 00 0d 00 20 00 1e 04 03 05 03 + 06 03 02 03 08 04 08 05 08 06 04 01 05 01 06 01 02 01 04 02 05 + 02 06 02 02 02 00 2d 00 02 01 01 00 1c 00 02 40 01 + """) + t = TLS(clientHello) -privkey = X25519PrivateKey._from_private_bytes(x25519_client_privkey) +assert(len(t.msg) == 1) +assert(t.msg[0].msgtype == 1) +assert(t.msg[0].extlen == 141) +assert(len(t.msg[0].ext) == 8) +e = t.msg[0].ext +assert(isinstance(e[0], TLS_Ext_ServerName)) +assert(isinstance(e[1], TLS_Ext_RenegotiationInfo)) +assert(isinstance(e[2], TLS_Ext_SupportedGroups)) +assert(isinstance(e[3],TLS_Ext_KeyShare_CH)) +assert(len(e[3].client_shares) == 1) +assert(e[3].client_shares[0].group == 29) +assert(e[3].client_shares[0].key_exchange == x25519_clt_pub) +assert(isinstance(e[4], TLS_Ext_SupportedVersion_CH)) +assert(isinstance(e[5], TLS_Ext_SignatureAlgorithms)) +assert(isinstance(e[6], TLS_Ext_PSKKeyExchangeModes)) +assert(e[6].kxmodeslen == 1) +assert(len(e[6].kxmodes) == 1) +assert(e[6].kxmodes[0] == 1) +assert(isinstance(e[7], TLS_Ext_RecordSizeLimit)) + += Decrypt a TLS 1.3 session with client authentication - Parse server Hello +# Values from RFC8448, section 6 +x25519_srv_priv = clean(""" + 73 82 a5 ad 1c dd 20 56 ae 18 cc 70 8b + d0 07 d9 81 30 db e2 cd 4d 9e ad 9b 96 95 2b ec bb 08 88 + """) + +x25519_srv_pub = clean(""" + 6c 2e 50 e8 65 91 9a 6b 5a 12 df af 91 8f + 92 b4 42 56 7b 0f 89 bc 54 47 8c 69 21 36 66 58 f0 62 + """) + +serverHello = clean(""" + 16 03 03 00 5a 02 00 00 56 03 03 3b + 50 fd f1 c3 d5 72 e4 0e 68 95 3e 7f ff 4e 27 58 45 9c 59 af a0 + 58 2c 0e a0 32 87 42 55 fe 6e 00 13 01 00 00 2e 00 33 00 24 00 + 1d 00 20 6c 2e 50 e8 65 91 9a 6b 5a 12 df af 91 8f 92 b4 42 56 + 7b 0f 89 bc 54 47 8c 69 21 36 66 58 f0 62 00 2b 00 02 03 04 + """) + + +privkey = X25519PrivateKey.from_private_bytes(x25519_clt_priv) t.tls_session.tls13_client_privshares["x25519"] = privkey -x25519_server_privkey = clean(""" - 03d43f48ed52076f 4ce9bab73d1f39ec - 689cf304075829f5 2b90f9f13bea6f34 - """) +t = TLS(serverHello, tls_session=t.tls_session.mirror()) +assert(len(t.msg) == 1) +assert(isinstance(t.msg[0], TLS13ServerHello)) +assert(len(t.msg[0].ext) == 2) +e = t.msg[0].ext +assert(isinstance(e[0], TLS_Ext_KeyShare_SH)) +assert(e[0].server_share.group == 29) +assert(e[0].server_share.key_exchange == x25519_srv_pub) +assert(isinstance(e[1], TLS_Ext_SupportedVersion_SH)) + += Decrypt a TLS 1.3 session with client authentication - Handshake traffic secret derivation + +# Values from RFC8448, section 6 +early_secret = clean(""" + 33 ad 0a 1c 60 7e c0 3b 09 e6 cd 98 93 68 0c + e2 10 ad f3 00 aa 1f 26 60 e1 b2 2e 10 f1 70 f9 2a + """) + +ecdhe_secret = clean(""" + 7d c1 14 f6 47 5d fa 79 77 be 73 6e f7 cb eb c4 + 8c 70 32 9e 8e 9a 74 b4 d7 03 3c 43 f9 59 7d 4f + """) + +handshake_secret = clean(""" + d9 95 24 36 74 fb 64 00 d7 d3 7b c0 e9 86 1b + db d9 ed 09 56 01 dc f2 99 48 74 f2 80 3d e2 2e 39 + """) + +client_handshake_traffic_secret = clean(""" + ce c7 a3 0c 68 72 07 0f 22 a7 ee b0 65 76 + 8d b6 7c 45 e2 95 33 db 87 99 08 ce 6d c6 6f 59 11 de + """) + +server_handshake_traffic_secret = clean(""" + 8b 02 d3 c0 04 42 a2 72 2c 40 98 eb e8 67 + 5b 23 e8 01 51 0f 0d 7e d7 78 d8 eb 0b 8f 42 a1 9a 5e + """) + +assert(len(t.tls_session.tls13_derived_secrets)) +assert(t.tls_session.tls13_early_secret is not None) +assert(t.tls_session.tls13_early_secret == early_secret) +assert(t.tls_session.tls13_dhe_secret == ecdhe_secret) +assert(t.tls_session.tls13_handshake_secret is not None) +assert(t.tls_session.tls13_handshake_secret == handshake_secret) +assert( 'client_handshake_traffic_secret' in t.tls_session.tls13_derived_secrets) +assert( t.tls_session.tls13_derived_secrets['client_handshake_traffic_secret'] == client_handshake_traffic_secret) +assert( 'server_handshake_traffic_secret' in t.tls_session.tls13_derived_secrets) +assert(t.tls_session.tls13_derived_secrets['server_handshake_traffic_secret'] == server_handshake_traffic_secret) + + += Decrypt a TLS 1.3 session with client authentication - Server handshake traffic key calculation + +# Values from RFC8448, section 6 +server_hs_traffic_key = clean(""" + 6c b6 e6 06 19 d8 c7 35 5c 5d 4c 4b c2 + be 90 d5 + """) +server_hs_traffic_iv = clean(""" + 64 f2 39 53 0c 3b 88 8f de 85 e0 be + """) + +assert(t.tls_session.prcs.cipher.key == server_hs_traffic_key) +assert(t.tls_session.prcs.cipher.fixed_iv == server_hs_traffic_iv) + + += Decrypt a TLS 1.3 session with client authentication - Decrypt and parse server handshake +from scapy.layers.tls.handshake import TLS13CertificateRequest +from scapy.layers.tls.cert import PubKeyECDSA +# Values from RFC8448, section 6 +server_finished = clean(""" + 93 b7 0c df 47 81 98 5b 96 34 5c aa c7 01 + b4 e7 50 d3 04 2d f1 a6 89 d8 fa ca 81 22 51 11 3c 11 + """) + +serverEncHS = clean(""" + 17 03 03 02 16 6d 0a 7a c0 79 b3 2a + 94 aa 68 c4 e2 89 3e 8b d0 d3 c1 85 f5 49 c2 36 fb bc e3 d6 47 + f0 8f 3c 94 a2 bf 42 4d 87 08 88 36 05 ad 89 55 f9 77 18 b0 21 + 3d ea d1 3d fb 23 eb b8 38 1d a5 82 75 66 12 bc b5 a5 d4 08 47 + 71 9f be 9f 17 9b fa e6 56 f3 ec fd 59 a4 c0 d3 51 32 ce 41 8a + 7e 46 f6 b6 a6 06 22 f8 a6 c0 6b 28 d8 33 60 16 35 63 be 9c 37 + f9 7e b9 02 32 69 24 a7 2b 3e d8 c8 38 12 77 d1 58 1c ab 9c 37 + 15 ac 24 01 39 84 67 ad 7e bf ab 3d 0c 34 19 e7 50 10 4f 7d 62 + c5 02 79 01 f2 e4 cd 4c a5 b8 07 1e b0 3d 3c 73 2d 83 21 50 66 + df c4 d2 91 d4 c1 ff 3b 8d 7e 42 98 f6 77 d4 d5 1d ea 11 68 d8 + f1 6c b2 7b a4 02 66 31 3a 1f ed f9 e2 3c c7 7f 76 54 50 f9 e9 + 6f 05 d0 8f 3d a2 45 b1 4d 49 46 f0 7e c8 1e ed 6d 56 f2 6b d5 + 74 f0 b7 f7 c7 04 70 37 c1 6f ce 3b 23 75 4e 66 2f ad 73 e2 b7 + 21 3f 6a f2 96 76 9c 99 a1 d3 8e 62 32 e0 ec 8d c4 f8 4d 6a a6 + f7 de 38 87 be 00 57 86 2f 90 18 e0 ab 39 67 05 aa 40 90 ab 5f + 2d ff 63 25 a5 57 e7 32 0d 4e ff d4 6b b4 f9 97 d1 63 20 7c ce + 66 65 29 4a a4 46 55 41 e3 fe 37 ee 73 50 65 9e a5 50 d6 dc b6 + af 3c 51 88 52 c7 a1 4c 3c c1 5b c3 2b 32 73 bd f1 75 1d a1 84 + 20 31 35 b1 17 d3 00 20 4f b1 2d 58 ca 9a c3 4b 68 ec a2 70 30 + 83 2f 7a 4b 46 d2 a5 57 57 f6 3f e8 f6 e8 5a c4 74 69 e6 19 8d + a8 8a 64 58 6b f2 3c 69 59 0d e8 22 26 3b e7 5f d8 36 84 72 40 + c4 8f 8c 14 5c d6 bd 69 89 62 e7 ed c2 34 eb e5 92 31 35 1e ef + 8d 76 52 cf 3b 08 ab 3a f6 e5 ec 74 c5 8a 8d a3 4b 39 f9 b0 d6 + c4 27 9a 9a 1f 82 07 17 29 e7 05 9d d7 f7 b9 5b 94 33 c4 68 4c + e1 89 1a 6d 33 43 2d 52 ed db 0b 8c ee 91 81 d4 03 ec cc 12 99 + 1f 1a d4 aa 62 c3 60 49 71 3a 7b b1 35 fd da 66 61 a0 5a 93 f8 + c1 6f + """) + + +t = TLS13(serverEncHS, tls_session=t.tls_session) +assert(t.deciphered_len == 518) +assert(len(t.inner.msg) == 5) +m = t.inner.msg +assert(isinstance(m[0], TLSEncryptedExtensions)) +assert(len(m[0].ext) == 3) +assert(isinstance(m[0].ext[0], TLS_Ext_SupportedGroups)) +assert(isinstance(m[0].ext[1], TLS_Ext_RecordSizeLimit)) +assert(isinstance(m[0].ext[2], TLS_Ext_ServerName)) +assert(isinstance(m[1], TLS13CertificateRequest)) +assert(m[1].cert_req_ctxt_len == 0) +assert(m[1].cert_req_ctxt == b'') +assert(m[1].extlen == 36) +assert(len(m[1].ext) == 1) +assert(isinstance(m[1].ext[0], TLS_Ext_SignatureAlgorithms)) +assert(m[1].ext[0].sig_algs == [1027, 1283, 1539, 515, 2052, 2053, 2054, 1025, 1281, 1537, 513, 1026, 1282, 1538, 514]) +assert(isinstance(m[2], TLS13Certificate)) +assert(len(m[2].certs) == 1) +assert(m[2].certs[0].cert[1].sigAlg == 'ecdsa-with-SHA256') +assert(m[2].certs[0].cert[1].issuer['commonName'] == 'ecdsa256') +assert(isinstance(m[2].certs[0].cert[1].pubKey, PubKeyECDSA)) +assert(isinstance(m[3], TLSCertificateVerify)) +assert(m[3].msglen == 75) +assert(isinstance(m[3].sig, _TLSSignature)) +assert(m[3].sig.sig_alg == 1027) +assert(m[3].sig.sig_val == b'0E\x02!\x00\xd7\xa4\xd3K\xd5OU\xfe\xe1\xa8\x96%g\x8c=\xd5\xe5\xf6\r\xacs\xec\x94\x0c\\{\x93\x04\xa0 \x84\xa9\x02 (\x9fY^\xd4\x88\xb9\xach\x9a=\x19+\x1a\x8b\xb3\x8f4\xafxt\xc0Y\xc9\x80j\x1f8&\x93S\xe8') +assert(isinstance(m[4], TLSFinished)) +assert(m[4].vdata == server_finished) + += Decrypt a TLS 1.3 session with client authentication - Client handshake traffic key calculation + +# Values from RFC8448, section 6 +client_hs_traffic_key = clean(""" + 91 69 48 f7 28 d9 82 3f a4 1a 00 4d 08 + 3f 21 7f + """) +client_hs_traffic_iv = clean(""" + 64 15 3d 79 ba c9 ea 10 ca 5a 0a 88 + """) + +assert(t.tls_session.pwcs.cipher.key == client_hs_traffic_key) +assert(t.tls_session.pwcs.cipher.fixed_iv == client_hs_traffic_iv) + + += Decrypt a TLS 1.3 session with client authentication - Decrypt and parse client encrypted handshake +from scapy.layers.tls.cert import PubKeyRSA +# Values from RFC8448, section 6 +clientFinished = clean(""" + 9a fe 2b a2 f6 3a 09 d2 29 d8 a4 29 e5 b3 + 7f fd 9f cc 73 bd b5 91 1b 82 42 59 72 aa 28 92 44 0f + """) + +clientEncHS = clean(""" + 17 03 03 02 80 b4 6a 63 93 4e 67 38 + 41 ab af 26 74 03 bc 67 7f 6b 6d 2a 1e 2f 12 bb 5f 62 68 3b fe + 36 a8 26 73 f0 6d 62 87 dd d6 09 bc f2 f5 fd 32 25 92 3d 24 af + 3c 76 68 2c 18 0e e5 71 a1 7c a4 bf be 2f 51 0d c9 a0 e1 fc a5 + cf f2 ce e8 7d 11 cb 53 1a 6e f9 0b f5 30 9a 6b 63 bb bc 0b 88 + ea 45 10 3a 43 04 09 15 43 85 9f a1 1e c0 32 ed 87 34 44 cd 51 + 85 ea d5 f6 a7 64 20 f0 f0 28 6a ce f8 02 c8 e4 78 8c 23 27 5f + 1b 06 da 60 0f 4a 7d ec d0 bc 59 d7 be f1 0e 64 9a e3 26 90 39 + 7f c3 d4 ed 6f 30 f8 01 d8 cd 56 9b 71 ad 4f a0 5e a7 cf 2a c2 + df a1 50 d2 20 50 5d 40 11 b3 4d 09 d5 38 53 eb a6 1a 10 1e 4f + 8d ca 47 d8 17 1a 88 4b 19 25 9a 3d d4 8c 5a c1 41 98 3e dc 77 + 81 4d 25 e7 f6 6b bb db 90 96 83 92 66 e0 65 61 82 8e cf b2 7e + af d4 e9 e8 1a 0b 96 e3 bf a4 2d ae 5a d8 03 59 b9 a6 66 14 02 + c3 a2 10 41 77 03 01 06 db d8 f6 5b b6 a0 15 9d 51 2e b1 3a f2 + 2a 25 9f 31 3b d5 8c 2e 21 fe 05 3d 57 f2 a9 62 b0 a4 ea 68 2c + 96 f7 0b 79 b5 60 13 61 92 82 3b 27 be 6a 2f b7 b1 c7 51 cc c0 + e3 30 36 15 54 14 85 b7 b3 07 b4 23 33 2c 11 ef a8 0b 72 f9 b8 + 0a 53 e5 3f 7b b3 8a 3a f4 c5 9f 80 08 ba d0 54 4e 56 14 e6 88 + ff 57 bc cd 69 35 f8 1f 44 7f 42 0c 1c 1b f4 05 88 18 e9 0b f5 + dc 71 6c ca e4 25 24 85 6d f8 25 0b cd bd 7a f6 5f 82 dd 53 06 + 1d 02 4f 6d 2f f5 c1 1e 37 92 a9 a7 0e 0e e2 a3 c2 0a 1b 96 8a + c3 91 f8 f9 28 31 13 5d 25 24 2a da 2f e2 41 c2 65 3e c9 96 33 + 9d fa 12 df ae 7a 33 73 df 88 b0 7c a2 7a ef 6d c2 66 a2 5f 13 + f7 5c 76 03 9c 1f 46 fd 7a 53 ae 63 99 c9 99 f4 b2 ae e1 8e 48 + 0d 6d 12 bf ae 22 6b bd c9 2a 6a d5 0b 4d 3b ac 7a bc 3b 36 51 + eb 5b e5 6f 33 bf 41 12 7b 3c a8 86 dc 71 4a 50 d1 49 03 57 bd + 40 d9 fd 6b e4 22 09 a4 dd b9 eb b2 98 7e 29 f1 20 f0 58 14 61 + 4d 2c 79 32 00 15 b4 61 fe 73 24 44 76 70 a1 af 5f 65 ca ed 15 + b4 74 ab 7f aa 49 50 16 ad f8 08 e5 3b 94 ef 54 af bb 0e 0a 3a + 27 32 ab 59 7f 7d 59 23 c7 73 86 aa 51 24 73 1f 8c c7 3e 70 3b + 34 1c 17 5a 45 49 39 a7 7a b6 43 13 c1 5c f3 fe 03 c4 f3 38 42 + 56 49 76 + """) + +t = TLS13(clientEncHS, tls_session=t.tls_session.mirror()) +assert(t.deciphered_len == 624) +assert(len(t.inner.msg) == 3) +m = t.inner.msg +assert(isinstance(m[0], TLS13Certificate)) +assert(m[0].cert_req_ctxt_len == 0) +assert(m[0].cert_req_ctxt == b'') +assert(m[0].certslen == 443) +assert(len(m[0].certs) == 1) +assert(m[0].certs[0].cert[0] == 438) +assert(m[0].certs[0].cert[1].cA == False) +assert(m[0].certs[0].cert[1].issuer['commonName'] == 'client') +assert(m[0].certs[0].cert[1].keyUsage == ['digitalSignature']) +assert(isinstance(m[0].certs[0].cert[1].pubKey, PubKeyRSA)) +assert(m[0].certs[0].cert[1].serial == 1) +assert(m[0].certs[0].cert[1].sigAlg == 'sha256WithRSAEncryption') +assert(m[0].certs[0].cert[1].subject['commonName'] == 'client') +assert(m[0].certs[0].extlen == 0) +assert(isinstance(m[1], TLSCertificateVerify)) +assert(isinstance(m[1].sig, _TLSSignature)) +assert(m[1].sig.sig_alg == 2052) +assert(m[1].sig.sig_len == 128) +assert(isinstance(m[2], TLSFinished)) +assert(m[2].vdata == clientFinished) +assert(t.inner.type == 22) + += Decrypt a TLS 1.3 session with client authentication - Application traffic secret derivation + +# Values from RFC8448, section 6 +master_secret = clean(""" + 57 c1 5d 7b 9d 44 1b 3d 40 a9 c6 ea 8a 3d 73 + 0e 07 b3 a1 ea 7a 33 39 ed 70 70 b9 a7 4a 3f 4f 28 + """) + +client_application_traffic_secret_0 = clean(""" + 73 c2 e8 90 fa 8d 06 72 58 d6 d5 0f a9 2f + e4 56 b0 98 cf 00 d9 72 7e ed 91 e8 89 2e f4 e6 f8 60 + """) + +server_application_traffic_secret_0 = clean(""" + c4 9a 91 fa f5 7f 8c 54 5d 50 48 a0 15 bf + 84 9f f6 39 42 e4 a7 ed cd 31 9f 8b 43 8a 97 c5 2e 21 + """) + + +exporter_master_secret = clean(""" + 05 2e 39 79 5e 5f 2b e6 e4 e0 97 4c fd d8 + 6c 6a 7a fe 3e 57 e5 58 98 10 a3 cc cf 64 29 58 be b2 + """) + +resumption_master_secret = clean(""" + 10 06 dc cb f4 0e b4 eb 97 8b ff 03 92 a9 + e4 52 a4 fb ad 58 aa 14 78 4d 5a 24 1c 6b 49 da cc fb + """) + + +assert(t.tls_session.tls13_master_secret is not None) +assert(t.tls_session.tls13_master_secret == master_secret) + +assert(len(t.tls_session.tls13_derived_secrets) == 9) +assert('client_traffic_secrets' in t.tls_session.tls13_derived_secrets) +assert(len(t.tls_session.tls13_derived_secrets['client_traffic_secrets']) == 1) +assert(t.tls_session.tls13_derived_secrets['client_traffic_secrets'][0] == client_application_traffic_secret_0) + +assert('server_traffic_secrets' in t.tls_session.tls13_derived_secrets) +assert(len(t.tls_session.tls13_derived_secrets['server_traffic_secrets']) == 1) +assert(t.tls_session.tls13_derived_secrets['server_traffic_secrets'][0] == server_application_traffic_secret_0) + +assert('exporter_secret' in t.tls_session.tls13_derived_secrets) +assert(t.tls_session.tls13_derived_secrets['exporter_secret'] == exporter_master_secret) + +assert('resumption_secret' in t.tls_session.tls13_derived_secrets) +assert(t.tls_session.tls13_derived_secrets['resumption_secret'] == resumption_master_secret) + += Decrypt a TLS 1.3 session with client authentication - Application traffic keys calculation + +# Values from RFC8448, section 6 +client_ap_traffic_key = clean(""" + cd c0 9c 80 6a a8 f8 6d fc d5 1e fc 44 + a0 c0 39 + """) + +client_ap_traffic_iv = clean(""" + 6e f8 52 e7 8b 46 d9 13 66 8e 53 e7 + """) + +server_ap_traffic_key = clean(""" + 88 b3 12 3d de ca df 8c 1b a2 98 e2 c1 + 81 76 b0 + """) + +server_ap_traffic_iv = clean(""" + 4e 09 78 51 3f 9d e8 32 7c 08 e4 f3 + """) + +# wcs instead of pwcs ? +assert(t.tls_session.rcs.cipher.key == client_ap_traffic_key) +assert(t.tls_session.rcs.cipher.fixed_iv == client_ap_traffic_iv) +assert(t.tls_session.wcs.cipher.key == server_ap_traffic_key) +assert(t.tls_session.wcs.cipher.fixed_iv == server_ap_traffic_iv) + += Decrypt a TLS 1.3 session with client authentication - Decrypt and parse client Alert + +# Values from RFC8448, section 6 +clientEncAlert = clean(""" + 17 03 03 00 13 e4 ad 7d 44 c2 92 45 + 33 9d 35 59 62 c7 79 b8 9e f4 4c 58 + """) + +t = TLS13(clientEncAlert, tls_session = t.tls_session) +assert(t.deciphered_len == 3) +assert(len(t.inner.msg) == 1) +assert(t.inner.type == 21) +m = t.inner.msg[0] +assert(isinstance(m, TLSAlert)) +assert(m.level == 1) +assert(m.descr == 0) + + += Decrypt a TLS 1.3 session with client authentication - Decrypt and parse server Alert + +# Values from RFC8448, section 6 +serverEncAlert = clean(""" + 17 03 03 00 13 1d ec c5 d6 e6 4b ba + 8a 6f 21 b4 fd 07 74 97 da 2a 90 cb + """) + +t = TLS13(serverEncAlert, tls_session = t.tls_session.mirror()) +assert(t.deciphered_len == 3) +assert(len(t.inner.msg) == 1) +assert(t.inner.type == 21) +m = t.inner.msg[0] +assert(isinstance(m, TLSAlert)) +assert(m.level == 1) +assert(m.descr == 0) + + +########### Compatibility mode ############################################### ++ Decrypt a TLS 1.3 session with compatibility mode + += Decrypt a TLS 1.3 session with compatibility mode - Parse client Hello +# Values from RFC8448, section 7 +x25519_clt_priv = clean(""" + de a0 0b 45 69 5d c7 81 f1 9d 34 a6 2c + 1a fd 31 ab 43 69 af 1e 85 5a 3b bb 25 8d 84 42 cd e6 d7 + """) + +x25519_clt_pub = clean(""" + 8e 72 92 cf 30 56 db b0 d2 5f cb e5 5c 10 + 7d c9 bb f8 3d d9 70 8f 39 20 3b a3 41 24 9a 7d 9b 63 + """) + +clientHello = clean(""" + 16 03 01 00 e0 01 00 00 dc 03 03 4e + 64 0a 3f 2c 27 38 f0 9c 94 18 bd 78 ed cc d7 55 9d 05 31 19 92 + 76 d4 d9 2a 0e 9e e9 d7 7d 09 20 a8 0c 16 55 81 a8 e0 d0 6c 00 + 18 d5 4d 3a 06 dd 32 cf d4 05 1e b0 26 fa d3 fd 0b a9 92 69 e6 + ef 00 06 13 01 13 03 13 02 01 00 00 8d 00 00 00 0b 00 09 00 00 + 06 73 65 72 76 65 72 ff 01 00 01 00 00 0a 00 14 00 12 00 1d 00 + 17 00 18 00 19 01 00 01 01 01 02 01 03 01 04 00 33 00 26 00 24 + 00 1d 00 20 8e 72 92 cf 30 56 db b0 d2 5f cb e5 5c 10 7d c9 bb + f8 3d d9 70 8f 39 20 3b a3 41 24 9a 7d 9b 63 00 2b 00 03 02 03 + 04 00 0d 00 20 00 1e 04 03 05 03 06 03 02 03 08 04 08 05 08 06 + 04 01 05 01 06 01 02 01 04 02 05 02 06 02 02 02 00 2d 00 02 01 + 01 00 1c 00 02 40 01 + """) + +t = TLS(clientHello) +assert(len(t.msg) == 1) +assert(t.msg[0].msgtype == 1) +assert(t.msg[0].sidlen == 32) +assert(t.msg[0].sid == b'\xa8\x0c\x16U\x81\xa8\xe0\xd0l\x00\x18\xd5M:\x06\xdd2\xcf\xd4\x05\x1e\xb0&\xfa\xd3\xfd\x0b\xa9\x92i\xe6\xef') +assert(t.msg[0].extlen == 141) +assert(len(t.msg[0].ext) == 8) +e = t.msg[0].ext +assert(isinstance(e[0], TLS_Ext_ServerName)) +assert(isinstance(e[1], TLS_Ext_RenegotiationInfo)) +assert(isinstance(e[2], TLS_Ext_SupportedGroups)) +assert(isinstance(e[3],TLS_Ext_KeyShare_CH)) +assert(len(e[3].client_shares) == 1) +assert(e[3].client_shares[0].group == 29) +assert(e[3].client_shares[0].key_exchange == x25519_clt_pub) +assert(isinstance(e[4], TLS_Ext_SupportedVersion_CH)) +assert(isinstance(e[5], TLS_Ext_SignatureAlgorithms)) +assert(isinstance(e[6], TLS_Ext_PSKKeyExchangeModes)) +assert(e[6].kxmodeslen == 1) +assert(len(e[6].kxmodes) == 1) +assert(e[6].kxmodes[0] == 1) +assert(isinstance(e[7], TLS_Ext_RecordSizeLimit)) + + += Decrypt a TLS 1.3 session with compatibility mode - Parse server Hello +# Values from RFC8448, section 7 +x25519_srv_priv = clean(""" + 01 7c 38 a3 64 79 21 ca 2d 9e d6 bd 7a + e7 13 2b 94 21 1b 13 31 bb 20 8c 8c cd d5 15 56 40 99 95 + """) + +x25519_srv_pub = clean(""" + 3e 30 f0 f4 ba 55 1a fd 62 76 83 41 17 5f + 52 65 e4 da f0 c8 84 16 17 aa 4f af dd 21 42 32 0c 22 + """) + serverHello = clean(""" - 1603010052020000 4e7f1298e3436403 - 8683391cbec1039a a0fba2f496d8c8e6 327151cc94bbc5ef - 7390751301002800 280024001d0020a2 0ed1b7f2d96a7f12 - 568f0e460bb0fc86 dc8d1db6c07d6b10 d4dc74aaac9219 - """) + 16 03 03 00 7a 02 00 00 76 03 03 e5 + dd 59 48 c4 35 f7 a3 8f 0f 01 30 70 8d c3 22 d9 df 09 ab d4 83 + 81 17 c1 83 a7 bb 6d 99 4f 2c 20 a8 0c 16 55 81 a8 e0 d0 6c 00 + 18 d5 4d 3a 06 dd 32 cf d4 05 1e b0 26 fa d3 fd 0b a9 92 69 e6 + ef 13 01 00 00 2e 00 33 00 24 00 1d 00 20 3e 30 f0 f4 ba 55 1a + fd 62 76 83 41 17 5f 52 65 e4 da f0 c8 84 16 17 aa 4f af dd 21 + 42 32 0c 22 00 2b 00 02 03 04 + """) + +privkey = X25519PrivateKey.from_private_bytes(x25519_clt_priv) +t.tls_session.tls13_client_privshares["x25519"] = privkey + t = TLS(serverHello, tls_session=t.tls_session.mirror()) +assert(len(t.msg) == 1) +assert(isinstance(t.msg[0], TLS13ServerHello)) +assert(t.msg[0].sidlen == 32) +assert(t.msg[0].sid == b'\xa8\x0c\x16U\x81\xa8\xe0\xd0l\x00\x18\xd5M:\x06\xdd2\xcf\xd4\x05\x1e\xb0&\xfa\xd3\xfd\x0b\xa9\x92i\xe6\xef') +assert(len(t.msg[0].ext) == 2) +e = t.msg[0].ext +assert(isinstance(e[0], TLS_Ext_KeyShare_SH)) +assert(e[0].server_share.group == 29) +assert(e[0].server_share.key_exchange == x25519_srv_pub) +assert(isinstance(e[1], TLS_Ext_SupportedVersion_SH)) + += Decrypt a TLS 1.3 session with compatibility mode - Parse server ChangeCipherSpec +from scapy.layers.tls.record import TLSChangeCipherSpec +# Values from RFC8448, section 7 +serverCCS = clean(""" 14 03 03 00 01 01 """) + +t = TLS13(serverCCS, tls_session=t.tls_session) + +assert(isinstance(t, TLS13)) +assert(t.type == 20) +assert(t.len == 1) +#assert(len(t.msg) == 1) +#assert(isinstance(t.msg[0], TLSChangeCipherSpec)) + + + += Decrypt a TLS 1.3 session with compatibility mode - Handshake traffic secret derivation +# Values from RFC8448, section 7 +early_secret = clean(""" + 33 ad 0a 1c 60 7e c0 3b 09 e6 cd 98 93 68 0c + e2 10 ad f3 00 aa 1f 26 60 e1 b2 2e 10 f1 70 f9 2a + """) + +ecdhe_secret = clean(""" + ee f7 90 55 90 77 db 5b b6 3b 66 84 e4 16 9f 05 + 1e 8f b3 4c e5 9b af ce 2f 9c 8e e6 8c c4 eb 79 + """) + +handshake_secret = clean(""" + f9 17 61 35 4a 67 e9 b0 7c 6d cc 3a 55 70 7e + fa 69 c4 51 9d 80 40 e5 f2 15 12 1e 0d f6 9a fa 4a + """) + +client_handshake_traffic_secret = clean(""" + 2c 3c b2 4a 10 81 ed b5 95 18 ee 68 61 e8 + 9a 6b 72 b3 80 1a fe 77 13 e4 cb bc 21 c0 79 5b f8 31 + """) + +server_handshake_traffic_secret = clean(""" + ca ce 3d 55 5c c1 c5 77 cf 97 0c ff 28 cf + 97 8d 6a 98 00 08 54 42 e1 8d 69 5b 50 f3 15 1d 18 c8 + """) + +assert(len(t.tls_session.tls13_derived_secrets)) +assert(t.tls_session.tls13_early_secret is not None) +assert(t.tls_session.tls13_early_secret == early_secret) +assert(t.tls_session.tls13_dhe_secret == ecdhe_secret) +assert(t.tls_session.tls13_handshake_secret is not None) +assert(t.tls_session.tls13_handshake_secret == handshake_secret) +assert( 'client_handshake_traffic_secret' in t.tls_session.tls13_derived_secrets) +assert( t.tls_session.tls13_derived_secrets['client_handshake_traffic_secret'] == client_handshake_traffic_secret) +assert( 'server_handshake_traffic_secret' in t.tls_session.tls13_derived_secrets) +assert(t.tls_session.tls13_derived_secrets['server_handshake_traffic_secret'] == server_handshake_traffic_secret) + += Decrypt a TLS 1.3 session with compatibility mode - Server handshake traffic key calculation +# Values from RFC8448, section 7 +server_hs_traffic_key = clean(""" + 04 10 91 fd ab 29 f2 c8 ab fb 15 6d c5 + fc 8d 54 + """) +server_hs_traffic_iv = clean(""" + 74 64 d7 91 68 5d e0 59 98 fc ba db + """) + +assert(t.tls_session.rcs.cipher.key == server_hs_traffic_key) +assert(t.tls_session.rcs.cipher.fixed_iv == server_hs_traffic_iv) + += Decrypt a TLS 1.3 session with compatibility mode - Decrypt and parse server encrypted handshake + +# Values from RFC8448, section 7 +server_finished = clean(""" + c8 c3 a8 f1 bf f5 27 40 61 f4 bc 3a 7c af + fb dc 96 16 09 4c a6 25 ca a6 5f 8e 76 ed 46 db 74 d3 + """) serverEncHS = clean(""" - 170301029c4e1f34 2dba17a54a09f7a1 - 8ffb2c6a29df17a6 db843044c52861bf 78988527ce366159 - e6a24871b704d2b9 fade56488921796d 719173a753bdfec8 - 0554c8c15e128695 450ccfdde1204ffd 2fb1ecdcd87b8070 - 644eb5a6b86ec951 aba3ed314754a2f3 14d4d2620b92da1f - 28f24b9559d76b67 a7b35c17cc231ba5 77a94fb2be59c74f - 84c8c78bf5faf4cb b2f8a37091580743 3c67d9f4e1b1923a - 3969b85a2ae9064e 34e84363aae43aa9 f58717836a017b9c - 33c3ad733c2fd3ce 288ae362764403d0 102a371047d9e49d - f9b30596262b1704 f0e9839fff5641ba a7041a4bcf9e4d46 - 7108922fc0ea0bc1 48dab2ebdd155f51 76c632be04a7c610 - 3fbc92754dba7962 4f8a09f8e8d65c17 eee87f98636fbc93 - bb734674b80d183c da904200a20d8f15 0a214902b6953209 - aa2431c3973bda3b d92a33878baca7b9 0507f433a55f2fe8 - f0db81898ebacf31 b68eaabfa27c39b6 a2453a322c005030 - 4e60bf53f0402b38 65b43fe5a7454c13 17a2dc76d1323fb1 - aa553996876a0dfe 8e789d6adf3dc85b 0636bb58a96e6aad - 851e7a6fc1dfa796 ec65e33bf9e3c05d 6de35f11e1f32731 - fb9550a60cb75e90 9345eb0edb81f99f cad883cb41d4a3ef - 7cbe671b92a8176b 472772be401b83a4 99b06b7ab0a1d9cd - 795e5ba0b67ce2d6 5c45565028824aa2 08797f405bbcf243 - 27dd69a1d986032f 544b15d110e4d8c4 681cb85c09960adb - 57fb9723eef0e0bb 275552af25fbdfc1 a4215adf14a9dba2 - 4462dd095f1a78f5 6ed6db3de139936f 14b091ab7f4adc81 - c277e68bfb6fd925 d92c06c0a4ddd105 9c071073a8a2e987 - f98948599f27bf6d 1f4369ac6c5a3323 2932fb8aa52ec4e1 - 85790dff0ef5eee0 13b4e90b5bc1cd4a c42b7ce82d856cc0 - f5d1c80400e68d61 b434cec56d437141 1e31849d4cf88862 - 8ba288548df6a19e c4 - """) -t = TLS(serverEncHS, tls_session=t.tls_session) + 17 03 03 02 a2 48 de 89 1d 9c 36 24 + a6 7a 6c 6f 06 01 ab 7a c2 0c 1f 6a 9e 14 d2 e6 00 7e 99 9e 13 + 03 67 a8 af 1b cf ea 94 98 fb ce 19 df 45 05 ee ce 3a 25 da 52 + 3c be 55 ea 1b 3b da 4e 91 99 5e 45 5d 50 0a 4f aa 62 27 b7 11 + 1e 1c 85 47 e2 d7 c1 79 db 21 53 03 d2 58 27 f3 cd 18 f4 8f 64 + 91 32 8c f5 c0 f8 14 d3 88 15 0b d9 e9 26 4a ae 49 1d b6 99 50 + 69 be a1 76 65 d5 e0 c8 17 28 4d 4a c2 18 80 05 4c 36 57 33 1e + 23 a9 30 4d c8 8a 15 c0 4e c8 0b d3 85 2b f7 f9 d3 c6 61 5b 15 + fa c8 3b bc a0 31 c6 d2 31 0d 9f 5d 7a 4b 02 0a 4f 7c 19 06 2b + 65 c0 5a 1d 32 64 b5 57 ec 9d 8e 0f 7c ee 27 e3 6f 79 30 39 de + 8d d9 6e df ca 90 09 e0 65 10 34 bf f3 1d 7f 34 9e ec e0 1d 99 + fc b5 fc ab 84 0d 77 07 c7 22 99 c3 b5 d0 45 64 e8 80 a3 3c 5e + 84 6c 76 2e 3d 92 2b b5 53 03 d1 d8 7c c0 f0 65 73 f1 7d cb 9b + 8f fd 35 bb d8 83 c1 cb 3a a2 4f cc 32 50 05 f7 68 ce 2f b6 24 + ca 97 b6 c4 d9 8e 17 f3 5b c2 c7 94 0a 06 10 0c 2d 44 8d b7 18 + 0b 2d 86 21 64 43 5c 9c 21 0e 98 60 39 4e 05 aa b2 3f f1 b0 20 + 3f 66 2c 58 8d a5 bc 44 11 47 7a 30 b4 11 36 c4 88 a0 a6 3f ca + b5 c1 5a c6 13 22 6d ae 82 7a 1d 1f e9 5e ce 6b 30 bc ee 15 60 + a8 d4 08 d2 64 55 5e 76 0f 9b fc 62 4c 2c 87 fd 04 56 c9 bf b4 + 1b cd 1a 7b 21 27 86 d2 b6 7f d5 78 04 fa cf a1 ee f7 cf 29 19 + d8 b9 98 c9 78 9f 76 3b 4d 9c aa 09 3a 9d ed 43 17 5d 46 a7 6b + 4d 54 f0 ce 0c 5d 22 59 b6 07 e3 0a 9d 24 12 63 87 4f a5 9d 6f + 57 0d c4 0d 83 a2 d8 3b f9 e9 85 0d 45 4c 57 80 65 35 a8 99 8a + e0 35 7d f9 2f 00 b9 66 73 44 c2 41 14 cc c9 ef 53 91 24 b2 04 + e7 e6 e7 48 c3 0a 28 a3 d1 d1 83 99 72 43 ea cc bb d3 3b 0c 11 + 15 a0 32 71 06 a1 e6 a7 52 71 d4 98 30 86 f6 32 ff 0e b8 b4 c6 + 31 02 cb ce f5 bb 72 da e1 27 9d 5d e8 eb 19 09 6d 8c db 07 fa + 8e a9 89 78 8f ac 23 e6 6e 04 88 c1 93 f3 f3 fe a8 c8 83 88 96 + bf 3a e4 b6 84 8d 42 ce d4 bd f4 1a be 6f c3 31 b4 42 25 e7 a1 + f7 d3 56 41 47 d5 45 8e 71 aa 90 9c b0 2b e9 58 bb c4 2e 3a a5 + a2 7c c6 ea f4 b6 fe 51 ae 44 95 69 4d 8a b6 32 0a ab 92 01 83 + fd 5b 31 a3 59 04 2f bd 67 39 1e c5 e4 d1 89 2a 2e 52 10 14 1a + 49 4e 93 01 b2 4a 11 3c 47 4c 7f 2a 73 45 78 47 + """) + +t = TLS13(serverEncHS, tls_session=t.tls_session) +assert(t.deciphered_len == 658) +assert(len(t.inner.msg) == 4) +m = t.inner.msg +assert(isinstance(m[0], TLSEncryptedExtensions)) +assert(len(m[0].ext) == 3) +assert(isinstance(m[0].ext[0], TLS_Ext_SupportedGroups)) +assert(isinstance(m[0].ext[1], TLS_Ext_RecordSizeLimit)) +assert(isinstance(m[0].ext[2], TLS_Ext_ServerName)) +assert(isinstance(m[1], TLS13Certificate)) +assert(isinstance(m[2], TLSCertificateVerify)) +assert(isinstance(m[3], TLSFinished)) +assert(m[3].vdata == server_finished) += Decrypt a TLS 1.3 session with compatibility mode - Parse client ChangeCipherSpec +from scapy.layers.tls.record import TLSChangeCipherSpec +# Values from RFC8448, section 7 +clientCCS = clean(""" 14 03 03 00 01 01 """) + +t = TLS13(clientCCS, tls_session=t.tls_session.mirror()) +assert(t.type == 20) +assert(t.len == 1) +#assert(len(t.msg) == 1) +#assert(isinstance(t.msg[0], TLSChangeCipherSpec)) + + += Decrypt a TLS 1.3 session with compatibility mode - Decrypt and parse client encrypted handshake +# Values from RFC8448, section 7 clientFinished = clean(""" - 1703010035161e94 818226d7bd618063 - 0804644debc52bdd 661034243217ac45 a084228c82086baa - 4893ecfc969624d6 8e19d88c3e67ccb4 8bdf - """) -t = TLS(clientFinished, tls_session=t.tls_session.mirror()) - -serverRecord1 = clean(""" - 17030100bbe6b3e9 89df694688f29f5d - a42d9f56053fc6d2 f73ee23accad26f9 599ee4dcf4e0cf9e - de80128b48156a65 e5e47dee679a8401 1234862b6728fb12 - be5198d5c023d6f2 0c355fc417a5eade 1aff0bf9ecba14c8 - 7277ea7aeb30055e a4d9b37bc12f7517 27ca7a1efc9285f8 - ed5e9e3be42ff475 30f2b7347a90618b 6f7f4eba9b8b6564 - f2159fcfcf09e4b6 2b4b09bb129e7c76 5c877966ca66e5cd - a84cdb6087a07fc0 50c97f275568623c 5d0f459d2b1133d1 - d5d37cd441192da7 - """) -t = TLS(serverRecord1, tls_session=t.tls_session.mirror()) + 69 2c ab 15 5c c6 c1 00 ea d6 07 33 d0 61 + 7f 6f b0 9b 71 aa 1e 8c 9a cc bb bc 9e 8e d3 36 c1 dd + """) -clientRecord1 = clean(""" - 170301004341b540 bf5adeaf9d209001 - 9f0733e281964724 526678a1946852cf 6f586dffacf1151d - bf7c9262ef6ae960 4a423fff339fd7e4 0cc3e7604ae661f0 - afa2f775c3668867 - """) -t = TLS(clientRecord1, tls_session=t.tls_session.mirror()) -app_data = t.inner.msg[0] -assert(app_data.data == b'\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !"#$%&\'()*+,-./01') - -serverRecord2 = clean(""" - 17030100438c3168 1fb21f820ef0603c - dc3b9d3deedeb2bb 615aa418fb2590a0 9b0dec00c2299feb - 17c4206f89ab28d2 7a605e288ac9bd69 657593addd1046be - 51b23940f8746634 - """) -t = TLS(serverRecord2, tls_session=t.tls_session.mirror()) -app_data = t.inner.msg[0] -assert(app_data.data == b'\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !"#$%&\'()*+,-./01') +clientEncHS = clean(""" + 17 03 03 00 35 32 d0 30 e2 73 77 3a + 86 96 c7 99 98 1a f6 ce d0 7f 87 48 2e 81 56 5e 39 4e 87 c8 67 + f3 3d f3 d6 5b 75 06 f1 a6 26 af 91 d4 82 1d 5f 7a 1f 21 0e f8 + dd 3c 6d 16 + """) -clientRecord2 = clean(""" - 17030100131ce9b1 f21ba236bca94455 - ab2aad71c666534a - """) -t = TLS(clientRecord2, tls_session=t.tls_session.mirror()) -alert = t.inner.msg[0] -assert(isinstance(alert, TLSAlert)) -assert(alert.level == 1 and alert.descr == 0) - -serverRecord3 = clean(""" - 1703010013aabcdb 9d293d23fb00deb7 - 11b562afeddffeed - """) -t = TLS(serverRecord3, tls_session=t.tls_session.mirror()) -alert = t.inner.msg[0] -assert(isinstance(alert, TLSAlert)) -alert.level == 1 and alert.descr == 0 +t = TLS13(clientEncHS, tls_session=t.tls_session) +assert(t.deciphered_len == 37) +assert(len(t.inner.msg) == 1) +m = t.inner.msg +assert(isinstance(m[0], TLSFinished)) +assert(m[0].vdata == clientFinished) +assert(t.inner.type == 22) + + += Decrypt a TLS 1.3 session with compatibility mode - Application traffic secret derivation + +# Values from RFC8448, section 7 +master_secret = clean(""" + 62 81 12 da e2 f7 02 48 80 63 e4 2d e6 c8 50 + a5 c0 82 0b 90 90 3e 00 ab c3 18 75 da 03 d4 bc 5b + """) + +client_application_traffic_secret_0 = clean(""" + 74 3e 4c 6b 56 cf 39 09 d1 b0 6d 01 95 6c + cd 2c 4b 37 75 84 49 ae c4 1d 98 da e4 49 24 ea a2 99 + """) + +server_application_traffic_secret_0 = clean(""" + b6 b8 14 4a a3 35 ed 30 59 c0 c9 c8 f0 ec + ab f7 af c9 4a f6 64 3b de cd fd 92 10 18 8f ab 74 51 + """) + + +exporter_master_secret = clean(""" + fb 69 12 1c ea 33 4d b4 59 e1 22 72 d1 79 + ba ca 23 69 b6 43 d1 1a 6a c7 2b 8b 27 a5 c9 64 fe b1 + """) + +resumption_master_secret = clean(""" + 0b 5d 44 07 ce a0 a4 2a 3a 81 dd 47 76 47 + b7 fe 91 80 db 29 7e 51 14 f1 ad 87 96 b4 dc 47 50 04 + """) + + +assert(t.tls_session.tls13_master_secret is not None) +assert(t.tls_session.tls13_master_secret == master_secret) + +assert(len(t.tls_session.tls13_derived_secrets) == 9) +assert('client_traffic_secrets' in t.tls_session.tls13_derived_secrets) +assert(len(t.tls_session.tls13_derived_secrets['client_traffic_secrets']) == 1) +assert(t.tls_session.tls13_derived_secrets['client_traffic_secrets'][0] == client_application_traffic_secret_0) + +assert('server_traffic_secrets' in t.tls_session.tls13_derived_secrets) +assert(len(t.tls_session.tls13_derived_secrets['server_traffic_secrets']) == 1) +assert(t.tls_session.tls13_derived_secrets['server_traffic_secrets'][0] == server_application_traffic_secret_0) + +assert('exporter_secret' in t.tls_session.tls13_derived_secrets) +assert(t.tls_session.tls13_derived_secrets['exporter_secret'] == exporter_master_secret) + +assert('resumption_secret' in t.tls_session.tls13_derived_secrets) +assert(t.tls_session.tls13_derived_secrets['resumption_secret'] == resumption_master_secret) + + + += Decrypt a TLS 1.3 session with compatibility mode - Application traffic keys calculation + +# Values from RFC8448, section 7 +client_ap_traffic_key = clean(""" + 33 d7 f9 70 97 56 c9 66 48 8a d4 43 84 + 37 e6 73 + """) + +client_ap_traffic_iv = clean(""" + c5 f3 0d 34 b0 e9 1b 7d 6c 8e ea 65 + """) + +server_ap_traffic_key = clean(""" + ed c4 cb d0 04 1c 28 cc 71 67 44 1d 7c + a5 3e 6a + """) + +server_ap_traffic_iv = clean(""" + bf 6c 7d 8e 0a 95 45 b4 27 dc f1 39 + """) + +# wcs instead of pwcs ? +assert(t.tls_session.rcs.cipher.key == client_ap_traffic_key) +assert(t.tls_session.rcs.cipher.fixed_iv == client_ap_traffic_iv) +assert(t.tls_session.wcs.cipher.key == server_ap_traffic_key) +assert(t.tls_session.wcs.cipher.fixed_iv == server_ap_traffic_iv) + += Decrypt a TLS 1.3 session with compatibility mode - Decrypt and parse client Alert + +# Values from RFC8448, section 7 +clientEncAlert = clean(""" + 17 03 03 00 13 0f 62 91 55 38 2d ba + 23 c4 e2 c5 f7 f8 4e 6f 2e d3 08 3d + """) + +t = TLS13(clientEncAlert, tls_session = t.tls_session) +assert(t.deciphered_len == 3) +assert(len(t.inner.msg) == 1) +assert(t.inner.type == 21) +m = t.inner.msg[0] +assert(isinstance(m, TLSAlert)) +assert(m.level == 1) +assert(m.descr == 0) + + += Decrypt a TLS 1.3 session with compatibility mode - Decrypt and parse server Alert + +# Values from RFC8448, section 7 +serverEncAlert = clean(""" + 17 03 03 00 13 b7 25 7b 0f ec af 69 + d4 f0 9e 3f 89 1e 2a 25 d1 e2 88 45 + """) + +t = TLS13(serverEncAlert, tls_session = t.tls_session.mirror()) +assert(t.deciphered_len == 3) +assert(len(t.inner.msg) == 1) +assert(t.inner.type == 21) +m = t.inner.msg[0] +assert(isinstance(m, TLSAlert)) +assert(m.level == 1) +assert(m.descr == 0) +# Missing tests : KeyUpdate... \ No newline at end of file