fix(tls): allow TLS 1.2 for S7-1500 PLCs#763
Conversation
The TLS context forced minimum_version to TLS 1.3, but S7-1500 PLCs with firmware < V3.0 (e.g. 1512SP-1 FW V4.1.2) only support TLS 1.2. The ClientHello offered only TLS 1.3 cipher suites and versions, so the PLC rejected the connection with a TCP RST. Lower the minimum to TLS 1.2 so Python's ssl module auto-negotiates the highest mutually-supported version. Also surface the CreateObject return value: when non-zero the PLC is rejecting the session (typically because TLS is required), but the old code silently fell through to the misleading "V1-initial SessionKey" warning. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
did not change much: from s7 import Protocol, Client
import logging
logging.basicConfig(level=logging.DEBUG)
client = Client()
client.connect(
"172.25.250.129", rack=0, slot=1, use_tls=True, protocol=Protocol.S7COMMPLUS
)
# data = client.db_read(15, 0, 1)
# print(f"db7[0:2] = {data.hex()}")
client.disconnect()ive disabled password authentication currently |
|
with capture: |
OpenSSL 3.5+ enables ML-KEM (post-quantum) key exchange by default, producing ~1500-byte ClientHellos that S7-1500 PLCs cannot handle, causing connection resets during the TLS handshake. Restrict key exchange to secp256r1 (prime256v1) which is supported by all S7 firmware versions with TLS. Also apply the TLS 1.2 minimum version fix to the async client, which was missed in the prior commit. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
Thanks for testing this on real hardware! The root cause of the connection reset is post-quantum key exchange in the TLS ClientHello. OpenSSL 3.5+ enables ML-KEM (group I've pushed a fix (51c1546) that restricts key exchange to Also applied the TLS 1.2 minimum version fix to the async client, which was missed in the prior commit. Could you test again with the latest commit? |
|
OpenSSL offers TLS 1.3 by default, adding extensions (supported_versions, key_share, psk_key_exchange_modes) to the ClientHello that S7-1500 PLCs don't understand and RST the connection. Setting maximum_version to TLS 1.2 produces a clean TLS 1.2 ClientHello without these extensions. The set_ciphersuites() call (TLS 1.3 only) is now unnecessary and removed. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
Thanks for the TIA Portal screenshots — they confirm "Only allow secure PG/PC and HMI communication" is enabled, so TLS is required. The previous fix reduced the ClientHello from ~1500 to ~524 bytes by disabling post-quantum key exchange, but the PLC still RSTs because OpenSSL includes TLS 1.3 extensions ( New fix (3bbce48): pin Could you test again with the latest commit? |
|
nope: |
|
@Erik1000 Thanks for testing — the post-quantum key exchange (ML-KEM) was indeed the first issue (OpenSSL 3.5+ inflates the ClientHello to ~1500 bytes), but our earlier fixes already solved that. Your latest ClientHello is a clean 164-byte TLS 1.2 with no PQ artifacts. What's still wrong: decoding your ClientHello byte-by-byte, it contains 14 cipher suites (including ChaCha20-Poly1305 and DHE_RSA families), 20 signature algorithms (EdDSA, RSA-PSS-PSS, DSA), and extensions like I've pushed a fix that:
This shrinks the ClientHello from 164 to 127 bytes. The one thing Python's Can you test again with the latest push? |
S7 PLCs have a minimal TLS stack that rejects ClientHellos with unknown cipher suites or extensions. Restrict to ECDHE+AES-GCM (matching TIA Portal), disable session_ticket and encrypt_then_mac extensions. Shrinks ClientHello from 164 to 127 bytes. Also fix emulated server to accept TLS 1.2 (was requiring 1.3, incompatible with client TLS 1.2 cap from previous commit). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
I dont think the client hello succeeds yet? |
The PLC's minimal TLS stack rejects ClientHellos containing modern signature algorithms (Ed25519, Ed448, RSA-PSS) instead of ignoring them per RFC 5246. Use ctypes to call SSL_CTX_ctrl(SET_SIGALGS_LIST) to restrict to classic RSA/ECDSA with SHA-2 only. Also add RSA-only cipher suites as fallback for PLCs that don't support ECDHE key exchange, and drop ECDSA ciphers since PLCs use RSA certificates. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
Thanks for the capture @Erik1000 — it confirmed the root cause. The PLC sends a bare TCP RST (no TLS alert) ~1ms after receiving the ClientHello. The problem is the signature_algorithms extension: Python's OpenSSL advertises 20 algorithms including Ed25519, Ed448, and 6 RSA-PSS variants. The PLC's minimal TLS stack treats these unknown algorithms as a fatal error instead of ignoring them per the TLS 1.2 spec. I've pushed 2ed7d82 which:
However, the signature algorithms issue can't be fixed from Python code — CPython's openssl_conf = openssl_init
[openssl_init]
ssl_conf = ssl_configuration
[ssl_configuration]
system_default = system_default_sect
[system_default_sect]
Groups = x25519:secp256r1:secp384r1
SignatureAlgorithms = RSA+SHA256:RSA+SHA384:RSA+SHA512:ECDSA+SHA256:ECDSA+SHA384Then run with: OPENSSL_CONF=/path/to/s7-openssl.cnf python your_script.pyCould you try this combination? |
Add RSA-only cipher suites as fallback for PLCs that don't support ECDHE key exchange, and drop ECDSA ciphers (PLCs use RSA certs). The PLC's minimal TLS stack also rejects ClientHellos with modern signature algorithms (Ed25519, Ed448, RSA-PSS) that Python's OpenSSL advertises by default. CPython's ssl module doesn't expose SSL_CTX_set1_sigalgs_list, so document restricting them via OPENSSL_CONF — same approach as the post-quantum Groups fix (#752). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
this time in a container based on ghcr.io/astral-sh/uv:python3.14-bookworm-slim |
|
oh wait i didnt see your whole message |
|
same error: |
S7 PLCs may reject ClientHellos containing the extended_master_secret extension (RFC 7627). Set SSL_OP_NO_EXTENDED_MASTER_SECRET (0x00000001, OpenSSL 3.0+) to suppress it. On OpenSSL 1.1.1 this value is the deprecated SSL_OP_MICROSOFT_SESS_ID_BUG no-op, so the flag is harmless on older builds. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
I see the OPENSSL_CONF is working (sigalgs down to 5, cipher suites look correct) but the PLC still rejects. Let me decode what's left in your ClientHello: Pushed f2a9b69 — adds If it still fails after that, the remaining suspect is To test that theory, you can temporarily override the cipher string in your [system_default_sect]
Groups = x25519:secp256r1:secp384r1
SignatureAlgorithms = RSA+SHA256:RSA+SHA384:RSA+SHA512:ECDSA+SHA256:ECDSA+SHA384
CipherString = AES128-GCM-SHA256:AES256-GCM-SHA384This forces RSA-only ciphers, which removes Also — is "Secure communication" / TLS actually enabled on this PLC in TIA Portal? The InitSSL exchange succeeds but if TLS isn't configured on the PLC side it would still reject the handshake. |
ECDHE ciphers force OpenSSL to include ec_point_formats and supported_groups extensions in the ClientHello. Python's ssl module always advertises compressed EC point formats alongside uncompressed, and there is no API to suppress them. S7-1500 PLCs send a bare TCP RST when they see these extensions. Switching to RSA-only ciphers (AES-GCM + AES-CBC-SHA256) removes both extensions entirely, producing a minimal ClientHello with only the signature_algorithms extension remaining. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
@Erik1000 Pushed 8fa3384 — switches to RSA-only cipher suites, dropping all ECDHE ciphers. Why: ECDHE ciphers force OpenSSL to include What changes in the ClientHello:
The only remaining extension should be If it still RSTs, the last suspect is the ECDSA entries in |





Summary
_setup_ssl_context— S7-1500 FW < V3.0 only supports TLS 1.2, so the old setting caused the PLC to RST the connectionFixes #760
Test plan
use_tls=True🤖 Generated with Claude Code