From b67899a2c05b7401d78c8cd2a80e5d0176ffddc3 Mon Sep 17 00:00:00 2001 From: mushtaf saheb Date: Fri, 23 Oct 2026 15:30:21 +0530 Subject: [PATCH] tls: reject invalid ServerKeyExchange signature in TLS client TLSServerKeyExchange.post_dissection only logged a failed signature check, and TLSClientAutomaton continued the handshake. Store the result in the session and abort with a decrypt_error alert, as is already done for the TLS 1.3 CertificateVerify. AI-Assisted: yes (Claude Opus 5.5) --- scapy/layers/tls/automaton_cli.py | 12 +++++++++++- scapy/layers/tls/handshake.py | 1 + scapy/layers/tls/session.py | 1 + test/scapy/layers/tls/tlsclientserver.uts | 12 ++++++++++++ 4 files changed, 25 insertions(+), 1 deletion(-) diff --git a/scapy/layers/tls/automaton_cli.py b/scapy/layers/tls/automaton_cli.py index 5cdaf42142a..e79aa6391a2 100644 --- a/scapy/layers/tls/automaton_cli.py +++ b/scapy/layers/tls/automaton_cli.py @@ -474,7 +474,17 @@ def missing_ServerKeyExchange(self): @ATMT.state() def HANDLED_SERVERKEYEXCHANGE(self): - pass + if (not self.cur_session.prcs.key_exchange.anonymous and + not self.cur_session.server_kx_sig_valid): + raise self.INVALID_SERVERKEYEXCHANGE() + + @ATMT.state() + def INVALID_SERVERKEYEXCHANGE(self): + self.vprint("Invalid ServerKeyExchange signature!") + self.add_record() + self.add_msg(TLSAlert(level=2, descr=51)) + self.flush_records() + raise self.FINAL(reason=self.INVALID_SERVERKEYEXCHANGE) def should_handle_CertificateRequest(self): """ diff --git a/scapy/layers/tls/handshake.py b/scapy/layers/tls/handshake.py index 847ae10d894..636597419e7 100644 --- a/scapy/layers/tls/handshake.py +++ b/scapy/layers/tls/handshake.py @@ -1104,6 +1104,7 @@ def post_dissection(self, pkt): 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]) + s.server_kx_sig_valid = sig_test if not sig_test: pkt_info = pkt.firstlayer().summary() log_runtime.info("TLS: invalid ServerKeyExchange signature [%s]", pkt_info) # noqa: E501 diff --git a/scapy/layers/tls/session.py b/scapy/layers/tls/session.py index 16baea88909..42252cec9ca 100644 --- a/scapy/layers/tls/session.py +++ b/scapy/layers/tls/session.py @@ -429,6 +429,7 @@ def __init__(self, self.server_key = None self.server_rsa_key = None self.server_cert_verify_valid = None + self.server_kx_sig_valid = None # self.server_ecdsa_key = None # A dictionary containing keys extracted from a NSS Keys Log using diff --git a/test/scapy/layers/tls/tlsclientserver.uts b/test/scapy/layers/tls/tlsclientserver.uts index 8b782a43884..18b7c4fa7e9 100644 --- a/test/scapy/layers/tls/tlsclientserver.uts +++ b/test/scapy/layers/tls/tlsclientserver.uts @@ -71,6 +71,10 @@ def run_tls_test_server(expected_data, q, curve=None, cookie=False, client_auth= elif sigalgo == "ed25519": mycert = scapy_path("/test/scapy/layers/tls/pki/srv_cert_ed25519.pem") mykey = scapy_path("/test/scapy/layers/tls/pki/srv_key_ed25519.pem") + elif sigalgo == "rsa_wrong_key": + # Sign with a key that doesn't match the certificate + mycert = scapy_path("/test/scapy/layers/tls/pki/srv_cert.pem") + mykey = scapy_path("/test/scapy/layers/tls/pki/cli_key.pem") else: raise ValueError print(mykey) @@ -383,6 +387,14 @@ test_tls_client("009e", "0303") test_tls_client("c016", "0303") += Testing TLS client with TLS 1.2 and TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 and an invalid ServerKeyExchange signature + +try: + test_tls_client("c02f", "0303", sigalgo="rsa_wrong_key") + assert False, "Client should have rejected the ServerKeyExchange" +except ValueError as ex: + assert "INVALID_SERVERKEYEXCHANGE" in str(ex) + = Testing TLS server and client with TLS 1.3 and TLS_AES_128_GCM_SHA256 test_tls_client("1301", "0304")