Skip to content

fix(tls): allow TLS 1.2 for S7-1500 PLCs#763

Open
gijzelaerr wants to merge 8 commits into
masterfrom
fix/tls-1.2-s7-1500
Open

fix(tls): allow TLS 1.2 for S7-1500 PLCs#763
gijzelaerr wants to merge 8 commits into
masterfrom
fix/tls-1.2-s7-1500

Conversation

@gijzelaerr

Copy link
Copy Markdown
Owner

Summary

  • Lower TLS minimum version from 1.3 → 1.2 in _setup_ssl_context — S7-1500 FW < V3.0 only supports TLS 1.2, so the old setting caused the PLC to RST the connection
  • Surface CreateObject return value when non-zero — the PLC rejects the session (e.g. TLS required) but the old code silently fell through to a misleading "V1-initial SessionKey" warning

Fixes #760

Test plan

  • Existing tests pass (TLS tests still negotiate 1.3 against the emulated server since both sides support it)
  • @Erik1000 confirms S7-1500 1512SP-1 FW V4.1.2 connects with use_tls=True

🤖 Generated with Claude Code

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>
@Erik1000

Erik1000 commented Jul 6, 2026

Copy link
Copy Markdown

did not change much:

DEBUG:snap7.connection:TCP connected to 172.25.250.129:102
DEBUG:snap7.connection:Sent COTP Connection Request
DEBUG:snap7.connection:Negotiated PDU size: 1024
DEBUG:snap7.connection:Unsupported COTP parameter: code=0xc1, length=2
DEBUG:snap7.connection:Unsupported COTP parameter: code=0xc2, length=16
DEBUG:snap7.connection:Received COTP Connection Confirm
INFO:snap7.connection:Connected to 172.25.250.129:102, PDU size: 1024
DEBUG:s7.connection:=== InitSSL === sending (26 bytes): 72 01 00 12 31 00 00 05 b3 00 00 00 00 00 00 00 00 30 00 00 00 00 72 01 00 00
DEBUG:snap7.connection:Sent 33 bytes: 03 00 00 21 02 f0 80 72 01 00 12 31 00 00 05 b3 00 00 00 00 00 00 00 00 30 00 00 00 00 72 01 00 00
DEBUG:snap7.connection:Received TPKT: version=3 length=31 payload (27 bytes): 02 f0 80 72 01 00 10 32 00 00 05 b3 00 00 00 00 70 00 00 00 00 00 00 72 01 00 00
DEBUG:s7.connection:=== InitSSL === received (24 bytes): 72 01 00 10 32 00 00 05 b3 00 00 00 00 70 00 00 00 00 00 00 72 01 00 00
DEBUG:s7.connection:InitSSL response: version=V1, data_length=16
DEBUG:s7.connection:InitSSL response body (20 bytes): 32 00 00 05 b3 00 00 00 00 70 00 00 00 00 00 00 72 01 00 00
DEBUG:snap7.connection:Sent 1505 bytes: 03 00 05 e1 02 f0 80 16 03 01 05 d5 01 00 05 d1 03 03 12 b7 43 25 17 61 e6 d9 85 cc b3 d4 aa 49 07 49 9a a6 18 4f dc 12 6d 92 04 7e cf fc 72 97 86 1c 20 ef fc 7c 5c cc 2e 4c 12 9d b8 ee 15 9e 96 41 a0 02 e2 35 e4 f8 aa f3 b6 ca 2e 66 53 87 ef 3c 5a 00 22 13 02 13 03 13 01 c0 2c c0 30 c0 2b c0 2f cc a9 cc a8 c0 24 c0 28 c0 23 c0 27 00 9f 00 9e 00 6b 00 67 01 00 05 66 ff 01 00 01 00 00 0b 00 04 03 00 01 02 00 0a 00 12 00 10 11 ec 00 1d 00 17 00 1e 00 18 00 19 01 00 01 01 00 23 00 00 00 16 00 00 00 17 00 00 00 0d 00 36 00 34 09 05 09 06 09 04 04 03 05 03 06 03 08 07 08 08 08 1a 08 1b 08 1c 08 09 08 0a 08 0b 08 04 08 05 08 06 04 01 05 01 06 01 03 03 03 01 03 02 04 02 05 02 06 02 00 2b 00 05 04 03 04 03 03 00 2d 00 02 01 01 00 33 04 ea 04 e8 11 ec 04 c0 aa d7 c6 75 5b 41 99 e3 ba 70 fa c1 d9 74 9f ef 15 14 43 27 98 25 80 8e c1 62 9e cc d8 91 00 4b ac b8 6b c2 27 a0 26 1a e8 82 a9 e5 56 e3 c1 17 dc 34 a3 2c 51 09 2d c5 22 42 78 8c ca 46 06 8e 7c 7a cf bb 07 fa f9 c6 9f 22 6f 73 0b 1b 4b c0 af f2 76 85 b0 78 6d cd 30 a8 59 d7 4e d1 01 6d ad 74 88 38 2b 06 00 76 ad 8d 9b 62 f6 40 30 b6 30 af 51 8c 84 09 b8 12 c0 8a 15 a9 b6 bf 07 bb cf 0a 7c 88 1d ca 82 ae a0 94 be 4c 04 42 d6 5f 81 75 2d e8 60 22 af e0 0a 3e 05 3a 50 04 c8 f2 28 5f b2 b5 7b 57 8a 93 a7 a9 5f 1d 68 7e b7 8b c3 f9 05 bf d5 bb 3d 16 d1 a9 34 5c 3a c4 80 60 c5 f6 47 db 35 c6 cf b5 8e ee 47 b4 47 1a 2d 31 76 ab 5f 45 32 74 bc 09 a3 73 68 74 a5 87 69 2b 78 ea a2 4a 23 c5 c9 27 98 5d 2a 20 9e e7 f5 2d f3 1b 41 99 f6 30 2b 76 4a b3 39 53 6a 8c 2d f6 b8 c8 8b 6c 32 0d 28 b6 b7 23 07 99 a3 34 b6 c8 ce 0c 69 b9 f4 90 86 83 36 98 94 89 58 a2 eb ac b6 49 87 14 7b 7d fa 45 ca 63 82 1b bc c4 24 bf 81 a0 aa 82 26 f0 fb 5b bc 70 4b d0 e4 6b 9c 44 cc 2d e5 c6 e6 01 6e 6e eb 07 fa dc 63 b2 db 1f b6 2b c4 be 74 5c e5 ea 3b 8e 04 4a 93 19 88 5f 69 61 cb 29 0b 96 2b 14 b9 91 8a 83 06 a9 c2 ea 73 2b 80 24 cf dc 08 78 25 a7 49 5b 51 de 7c 09 b9 3a 4f 20 e3 74 74 c1 90 9f 46 6b d5 c3 10 33 85 c8 85 a6 5c d2 ea 95 14 c6 b3 6a dc bf 5b b4 7e d7 e4 60 49 a3 c8 ef 1c 95 3e f1 54 65 24 b4 cd b5 92 0d 88 68 11 79 37 1a 93 93 4a b0 08 96 bb 29 cc 56 17 2e 32 bb dc 11 c1 16 91 b3 a9 40 85 67 73 6a 6b c9 55 ee fc 11 fb 8b 27 7e 68 c9 11 3a a8 6a 15 b1 3c 2a 21 8e 5b 07 ff 99 a4 d0 f9 cd 45 72 33 49 e4 40 d0 95 ce f7 08 1b 81 88 2e 2b d7 56 1b 39 40 9f cb 7f 67 22 31 06 b9 50 ef 91 55 69 7a 79 5c 14 4d 48 ac 1f 36 37 21 95 31 a3 aa 80 6a 54 d1 7c 3b 33 cf 49 56 be 07 11 83 f3 d1 7b 70 70 b1 a6 47 5a 8b 65 b5 68 fc 36 9b 1a 32 9d a8 0f 5d c0 ac 37 cb 66 36 f7 8d c7 ab 86 1c a2 33 1a 21 03 ee e8 11 cc ba b2 03 ea 70 3e 6a 13 45 62 61 9c 1b 86 4f 72 7a 6f b5 8b 83 10 98 ac 8c 8d 23 8b 25 fa 59 86 ff ba 9b de e2 8a 2b 67 b9 99 b3 7a a5 56 ce 78 85 5c d6 93 53 7a 6c 95 aa e1 01 24 2c 82 c2 91 6b 57 0b 81 eb 8b 2b ee 44 b7 c0 aa 58 16 90 4d de 43 b2 14 10 bf c3 46 70 99 4a aa a5 33 bb f3 7b 2f 5f 69 36 84 58 6d e9 31 20 7c 68 0b 76 e7 98 8b 64 18 57 45 4d 25 7c 03 cc aa 44 17 4a 39 ad a1 79 71 97 bb d8 bc c2 4e d9 b6 69 82 80 4f 49 8b ca 32 b9 f8 00 14 07 eb 0b 2f 28 8d 7f d5 95 1f 26 63 20 91 03 13 23 a1 56 17 0d 95 01 c1 1b 69 48 fe 9b 10 e0 64 44 f0 c0 b5 5f 15 6f 94 fb 5c e6 aa a0 ea c2 29 75 e6 47 a3 37 79 6c 62 00 5d e2 98 38 00 51 6e 1b 89 5e 27 44 ab 2c cf b7 6c c8 c8 ab 0b 53 b4 b8 f4 4a 70 b2 e2 3a da b4 2f 3b ba a6 8c 32 bf b4 e2 06 53 41 79 a0 a5 59 d5 85 2f c3 c7 9b 87 f6 5c cc b0 73 86 26 6c f5 b3 62 ce c9 63 44 c9 82 fc 8a 66 0a a1 cd 12 da 56 f9 09 ce 91 16 49 7f 1c 9f 0a 37 b5 40 5b af 92 91 57 6f 38 7a b9 cb 10 c5 44 57 cd 61 c8 2f f1 93 22 a1 1e 59 51 ab 23 d7 8c d3 73 bb 9f cb a8 7f 17 47 60 00 36 85 c2 be 34 13 ce 71 a6 39 fc 81 24 b8 d7 46 6d 8a ca 74 40 b3 d1 84 60 ea 19 3a 83 4b 4d d9 29 6c bd 57 6f bb a9 18 bc 90 5f 88 50 9e f3 95 c8 ab fc cc c4 84 91 22 68 bf 27 48 4e ed c9 87 e3 a1 6e 6b f6 1d ba 94 86 b9 b1 c2 3d 2b 39 fd 8a 2d f8 1b c5 9d 9c 9c cf 17 b2 d5 6c c5 97 30 2f db b9 08 59 0a 8f 65 b0 bb 63 f4 92 4b 94 35 3d ea 4b cd 16 11 d1 ba 03 04 98 4c d3 50 c4 f4 b7 7c 3c 65 7d e7 eb b0 20 98 63 1d 73 be 22 5a 40 f2 34 b2 6c e6 48 22 82 69 2f 95 98 41 cb c2 98 29 8f 23 71 4b 9a b4 bd 19 d1 62 b4 31 40 43 94 54 e3 20 57 f9 98 41 45 e5 5b 95 23 83 28 2c 6d bb 95 ae 9f b7 bc 72 b8 10 fd cb 0d fb 2a 4f 58 7c c9 77 96 3e 2e 35 3e 1e c1 43 2e de 23 aa ba fc d8 34 9b ef 45 a4 61 3b 56 65 1c 69 65 9a 52 d1 da 9d 98 05 99 76 25 f7 1d 89 56 c9 a5 1b fd c6 5b 02 96 ea c8 21 2e fd 23 6b 64 00 1d 00 20 f2 f2 52 a8 96 76 59 3e 33 d1 4e 54 92 4b 71 90 26 a8 7e c1 3d 32 35 ce ef 9a e2 57 07 5c dc 05
INFO:snap7.connection:Disconnected from 172.25.250.129:102
DEBUG:s7.client:S7CommPlus connection failed: Receive error: [WinError 10054] An existing connection was forcibly closed by the remote host
Traceback (most recent call last):
  File "<frozen runpy>", line 203, in _run_module_as_main
  File "<frozen runpy>", line 88, in _run_code
  File "X\s7_comm\__main__.py", line 10, in <module>
    client.connect(
    ~~~~~~~~~~~~~~^
        "172.25.250.129", rack=0, slot=1, use_tls=True, protocol=Protocol.S7COMMPLUS
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    )
    ^
  File "X\.venv\Lib\site-packages\s7\client.py", line 124, in connect
    raise RuntimeError(
        f"S7CommPlus connection to {address}:{tcp_port} failed and protocol=S7COMMPLUS was explicitly requested"
    )
RuntimeError: S7CommPlus connection to 172.25.250.129:102 failed and protocol=S7COMMPLUS was explicitly requested
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

@Erik1000

Erik1000 commented Jul 6, 2026

Copy link
Copy Markdown

with capture:

DEBUG:snap7.connection:TCP connected to 172.25.250.129:102
DEBUG:snap7.connection:Sent COTP Connection Request
DEBUG:snap7.connection:Negotiated PDU size: 1024
DEBUG:snap7.connection:Unsupported COTP parameter: code=0xc1, length=2
DEBUG:snap7.connection:Unsupported COTP parameter: code=0xc2, length=16
DEBUG:snap7.connection:Received COTP Connection Confirm
INFO:snap7.connection:Connected to 172.25.250.129:102, PDU size: 1024
DEBUG:s7.connection:=== InitSSL === sending (26 bytes): 72 01 00 12 31 00 00 05 b3 00 00 00 00 00 00 00 00 30 00 00 00 00 72 01 00 00
DEBUG:snap7.connection:Sent 33 bytes: 03 00 00 21 02 f0 80 72 01 00 12 31 00 00 05 b3 00 00 00 00 00 00 00 00 30 00 00 00 00 72 01 00 00
DEBUG:snap7.connection:Received TPKT: version=3 length=31 payload (27 bytes): 02 f0 80 72 01 00 10 32 00 00 05 b3 00 00 00 00 70 00 00 00 00 00 00 72 01 00 00
DEBUG:s7.connection:=== InitSSL === received (24 bytes): 72 01 00 10 32 00 00 05 b3 00 00 00 00 70 00 00 00 00 00 00 72 01 00 00
DEBUG:s7.connection:InitSSL response: version=V1, data_length=16
DEBUG:s7.connection:InitSSL response body (20 bytes): 32 00 00 05 b3 00 00 00 00 70 00 00 00 00 00 00 72 01 00 00
DEBUG:snap7.connection:Sent 1505 bytes: 03 00 05 e1 02 f0 80 16 03 01 05 d5 01 00 05 d1 03 03 53 1b d8 ea 68 10 d0 40 2a 08 88 c0 a5 35 d4 0d 5c 77 4b 30 c2 d7 86 51 6d 1f 73 9a 27 fc 17 e3 20 8f 28 d8 cc f0 f8 a5 8b 88 1d 11 35 0d a5 51 43 9e 88 c5 b9 7f ee 9d 33 07 f3 cb aa b0 70 18 c3 00 22 13 02 13 03 13 01 c0 2c c0 30 c0 2b c0 2f cc a9 cc a8 c0 24 c0 28 c0 23 c0 27 00 9f 00 9e 00 6b 00 67 01 00 05 66 ff 01 00 01 00 00 0b 00 04 03 00 01 02 00 0a 00 12 00 10 11 ec 00 1d 00 17 00 1e 00 18 00 19 01 00 01 01 00 23 00 00 00 16 00 00 00 17 00 00 00 0d 00 36 00 34 09 05 09 06 09 04 04 03 05 03 06 03 08 07 08 08 08 1a 08 1b 08 1c 08 09 08 0a 08 0b 08 04 08 05 08 06 04 01 05 01 06 01 03 03 03 01 03 02 04 02 05 02 06 02 00 2b 00 05 04 03 04 03 03 00 2d 00 02 01 01 00 33 04 ea 04 e8 11 ec 04 c0 d8 24 7f 42 9c 57 f9 52 9c 5b 80 93 e1 95 37 5a 84 32 10 0c 63 4b 28 64 ef 17 a0 4b 72 1b 55 9b 07 cb ac 37 32 fc 7e 1e 66 cd 5e 56 48 82 d1 9e 3c 88 39 63 32 92 fe d4 b4 c0 1a 47 fe 09 9f ef 03 86 15 b1 8b 03 6c 5e 23 25 98 65 89 40 06 c3 ad 08 fb 8e 1c 11 c7 3e 2b 9d 0b 3c 78 4d 52 4b 7b 86 9b 3f c3 5b cb 31 b0 ea 4a 1e 11 31 c5 12 46 a8 a2 d4 3a f8 c7 90 20 66 3b 54 b5 3e 65 26 8c 44 75 72 ab 05 22 1b 49 1a 3c d7 81 5a d2 4d c6 6c 10 5a 5c 4a 93 62 b3 67 01 06 81 16 a0 88 38 91 28 50 52 e0 e9 8c bb 35 bf 89 9a 56 fe 99 cf c8 13 a8 aa 4c 62 4d d6 08 d5 d8 87 58 54 7a b7 4a 2f 53 46 09 4c 31 7a 12 34 9f bb 14 30 8e 0b 31 c4 87 5f e5 48 0f ea 4b 6b 83 f1 b0 71 e7 0e 39 b5 88 49 e8 8f 12 d7 82 cd f2 88 7d e1 89 8b 92 6e 4f 2c 3c 8e 28 2d d5 fc bc 19 e6 76 33 2a 1b 1f 25 3c 66 07 49 d6 3c 23 10 01 37 50 ac 37 cd b7 72 3a 78 85 e0 98 05 8b 9b 21 c3 c8 a6 5a a8 8b cf 93 c5 ec 25 1a 1b dc 6f 11 30 23 61 13 5e 09 67 1e 02 56 c9 7d 04 aa 4e 8a 2c 1b 4c 26 46 f6 6a 52 79 41 15 73 49 5f c1 79 61 f1 17 a7 a7 bf 59 ec 5e 32 e8 79 32 b5 ad cc f2 54 b6 f9 30 a1 43 12 13 7c 3b 83 04 bc e4 34 79 9d 61 ce a9 01 bd eb b1 bb 12 44 73 07 76 65 17 24 c4 ef 47 0d e1 69 94 b9 b0 a4 de 7c ad b5 99 4f 90 2a 54 bc 80 b9 8d 13 3f 1d 19 be f9 97 0f b2 fa 59 fe 05 1e 16 d4 bd 32 43 68 77 d3 35 bd c9 3f 3b d0 44 33 30 88 0f 72 c5 6f d5 54 fa 42 9f 5e d8 4b 1d a9 b9 17 ba bf 63 a3 cf de b9 32 56 f1 b9 4a 9b 61 24 51 95 e9 e5 84 ff 2c 28 eb a8 2b 63 65 8e d9 8a 0e 34 14 89 1a 0a 5b f3 78 cd 53 d3 c5 8a e3 c8 58 ac 90 40 f8 73 af a2 ad c3 cb 47 07 08 32 6e 8b 82 7a 39 5b 96 b3 99 f1 c7 a5 eb 67 34 6c 34 a0 58 5b be da b6 15 ef 29 4a 2f 87 04 86 e1 57 1f 22 08 d5 76 42 3c f2 c3 a1 a1 0f 95 3a 4d b0 fc 92 e4 a8 7f c0 67 c6 9f b3 ac f3 b1 a8 72 64 2a 3a b7 c6 33 c5 95 30 93 0e d5 42 82 d5 f4 b7 1b 00 69 96 2c 6c 4e 23 95 45 6a ca 2d 67 3b 8c 3b 99 7c 92 0a 84 6a 50 0a 47 60 f8 aa 26 cf ac 3a c1 17 16 1c 14 5d 98 e8 a4 5d 07 7b 67 43 ab 0a 54 2c dc e5 03 b5 99 9b e5 d4 b4 be 65 ca db 14 4c c6 45 4e 10 74 9c 22 43 a8 16 91 9c f0 97 0d 15 d3 17 52 d2 51 0f 0c 25 9e 36 72 2d f5 a9 59 cc 0a d5 dc 23 86 71 a2 58 41 66 94 37 93 a6 c7 ac b0 31 94 a6 c8 20 7a b9 ae 6e 97 36 ca 41 36 31 62 08 a1 e1 24 98 d7 58 aa da 3a 88 db 43 b3 1b 39 ca f2 79 42 b0 ba 9f 0c 3b bf 85 0a b3 98 90 7d f5 69 52 03 51 5d 11 74 ca 01 2c b7 d9 ac 5e c2 31 46 ac 1b cc 7a 18 f2 c0 b8 8a c1 b3 55 a6 7a 07 c0 bd bc b1 07 f8 01 18 f7 6c 6b 68 44 cd 91 99 13 47 51 90 2f 01 0a 2e ab 20 a7 c9 58 9c f1 15 e3 17 05 5f e0 be 22 1a ad ab 64 22 11 c3 35 81 39 c8 89 72 cc 35 19 cb 2c 65 69 38 40 3a 92 8c 01 bd 64 be 75 03 ad 10 41 5f c0 99 1d 19 4b 04 bf 17 b4 30 e6 3a fb 6b 54 ae 53 25 70 52 02 ee 5b 2f 4a 17 05 86 b5 6f c3 30 6c 24 56 00 1c 98 8e 26 45 7d a3 80 75 1b 63 59 cb ca 8f e9 c7 0f 5d 43 21 d4 85 72 91 e1 b6 fe 62 c9 b5 d4 87 1d 0a 16 2a 90 a8 c2 47 88 82 37 cf 8c b7 2a d5 40 06 f7 00 3d e5 d9 1b e1 1b 74 b7 76 80 1d 21 96 4a 51 c2 06 22 8b 0c a1 9f d3 29 11 b7 cb 16 08 37 13 3b 66 96 d8 52 c1 55 81 7b 34 e8 5d 03 2a 21 fc f5 58 66 75 bf 9c 54 4e 99 70 59 cb b0 81 3c 29 1e 15 55 be 4c 97 90 ba 83 33 ba 48 68 d1 f6 9f 46 95 47 cd d7 02 b6 f5 95 b5 9c 25 9f 9c b0 39 55 36 35 46 c4 11 c6 35 68 54 1a d5 46 10 2a bc 5f 75 70 50 bf f8 8f dc d6 a9 a2 c5 54 04 e9 22 6c 5b b1 c5 42 77 d3 81 95 56 15 85 a0 f6 bd 1a f6 a4 44 39 bd a4 3a 46 80 35 76 a9 2c 6a aa 47 70 2f 1a b7 3c 0a 1d 00 99 4c 3c 22 00 1c 4c c1 55 f3 69 c7 b6 74 33 9b 9c 1b db 1b c9 16 64 a1 f5 ca 16 33 c9 81 bd d6 86 89 39 13 20 0c 73 e5 8f e7 0e 87 8a b2 7a 46 a5 3c c6 90 d9 cb 55 3a 49 7c f9 c6 27 07 a4 1c f2 98 4c 9e df 66 ca fb 17 eb 1d e9 8a 8b 3a de 6a 8d 98 1b 13 be 7f 63 c5 cc aa 4d 70 00 1d 00 20 30 a3 0a c4 bf e8 03 01 b1 ba 1a a3 a0 d9 6b 53 51 69 ba 0b 79 6e 24 d3 48 74 5c 8c 8d 72 01 3c
INFO:snap7.connection:Disconnected from 172.25.250.129:102
DEBUG:s7.client:S7CommPlus connection failed: Receive error: [WinError 10054] An existing connection was forcibly closed by the remote host
Traceback (most recent call last):
  File "<frozen runpy>", line 203, in _run_module_as_main
  File "<frozen runpy>", line 88, in _run_code
  File "X\s7_comm\__main__.py", line 10, in <module>
    client.connect(
    ~~~~~~~~~~~~~~^
        "172.25.250.129", rack=0, slot=1, use_tls=True, protocol=Protocol.S7COMMPLUS
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    )
    ^
  File "X\.venv\Lib\site-packages\s7\client.py", line 124, in connect
    raise RuntimeError(
        f"S7CommPlus connection to {address}:{tcp_port} failed and protocol=S7COMMPLUS was explicitly requested"
    )
RuntimeError: S7CommPlus connection to 172.25.250.129:102 failed and protocol=S7COMMPLUS was explicitly requested

S7_CommPlusFailure.zip

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>
@gijzelaerr

Copy link
Copy Markdown
Owner Author

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 0x11EC / SecP256r1MLKEM768) by default, which adds a ~1200-byte key share to the ClientHello — bloating it to ~1500 bytes. The S7-1500 PLC can't handle this oversized ClientHello and RSTs the connection.

I've pushed a fix (51c1546) that restricts key exchange to secp256r1 (prime256v1) via ctx.set_ecdh_curve("prime256v1"). This brings the ClientHello down to ~517 bytes.

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?

@Erik1000

Erik1000 commented Jul 7, 2026

Copy link
Copy Markdown
DEBUG:snap7.connection:TCP connected to 172.25.250.129:102
DEBUG:snap7.connection:Sent COTP Connection Request
DEBUG:snap7.connection:Negotiated PDU size: 1024
DEBUG:snap7.connection:Unsupported COTP parameter: code=0xc1, length=2
DEBUG:snap7.connection:Unsupported COTP parameter: code=0xc2, length=16
DEBUG:snap7.connection:Received COTP Connection Confirm
INFO:snap7.connection:Connected to 172.25.250.129:102, PDU size: 1024
DEBUG:s7.connection:=== InitSSL === sending (26 bytes): 72 01 00 12 31 00 00 05 b3 00 00 00 00 00 00 00 00 30 00 00 00 00 72 01 00 00
DEBUG:snap7.connection:Sent 33 bytes: 03 00 00 21 02 f0 80 72 01 00 12 31 00 00 05 b3 00 00 00 00 00 00 00 00 30 00 00 00 00 72 01 00 00
DEBUG:snap7.connection:Received TPKT: version=3 length=31 payload (27 bytes): 02 f0 80 72 01 00 10 32 00 00 05 b3 00 00 00 00 70 00 00 00 00 00 00 72 01 00 00
DEBUG:s7.connection:=== InitSSL === received (24 bytes): 72 01 00 10 32 00 00 05 b3 00 00 00 00 70 00 00 00 00 00 00 72 01 00 00
DEBUG:s7.connection:InitSSL response: version=V1, data_length=16
DEBUG:s7.connection:InitSSL response body (20 bytes): 32 00 00 05 b3 00 00 00 00 70 00 00 00 00 00 00 72 01 00 00
DEBUG:snap7.connection:Sent 524 bytes: 03 00 02 0c 02 f0 80 16 03 01 02 00 01 00 01 fc 03 03 64 27 ec 98 17 cc 68 c9 06 27 d8 16 ba b9 f4 98 77 f3 d3 88 90 24 e4 5d 01 0f 19 0e 9e 5b a1 bd 20 2e 3b 65 6e 25 78 bf ee 46 ff 12 60 84 2e 25 5e 4c 80 1c 37 6f 27 20 01 f1 35 0b 9a 74 e0 bf db 00 22 13 02 13 03 13 01 c0 2c c0 30 c0 2b c0 2f cc a9 cc a8 c0 24 c0 28 c0 23 c0 27 00 9f 00 9e 00 6b 00 67 01 00 01 91 ff 01 00 01 00 00 0b 00 04 03 00 01 02 00 0a 00 04 00 02 00 17 00 23 00 00 00 16 00 00 00 17 00 00 00 0d 00 36 00 34 09 05 09 06 09 04 04 03 05 03 06 03 08 07 08 08 08 1a 08 1b 08 1c 08 09 08 0a 08 0b 08 04 08 05 08 06 04 01 05 01 06 01 03 03 03 01 03 02 04 02 05 02 06 02 00 2b 00 05 04 03 04 03 03 00 2d 00 02 01 01 00 33 00 47 00 45 00 17 00 41 04 05 0e 81 30 06 9f 5b 8a c4 95 72 e8 79 91 4d 0d 8c 6d 9a c5 39 a9 eb 21 19 e8 8f af c1 9d f1 1e b7 93 9c 5a a8 cd 63 40 9c 7d 2d 36 03 6b 48 22 1d 13 cf 30 f3 b3 9f 0d 28 51 0b f3 cc df 07 e2 00 15 00 d8 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 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
INFO:snap7.connection:Disconnected from 172.25.250.129:102
DEBUG:s7.client:S7CommPlus connection failed: Receive error: [WinError 10054] An existing connection was forcibly closed by the remote host
Traceback (most recent call last):
  File "<frozen runpy>", line 203, in _run_module_as_main
  File "<frozen runpy>", line 88, in _run_code
  File "X\s7_comm\__main__.py", line 10, in <module>
    client.connect(
    ~~~~~~~~~~~~~~^
        "172.25.250.129",
        ^^^^^^^^^^^^^^^^^
    ...<3 lines>...
        protocol=Protocol.S7COMMPLUS,
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    )

    ^
  File "X\.venv\Lib\site-packages\s7\client.py", line 124, in connect
    raise RuntimeError(
        f"S7CommPlus connection to {address}:{tcp_port} failed and protocol=S7COMMPLUS was explicitly requested"
    )
RuntimeError: S7CommPlus connection to 172.25.250.129:102 failed and protocol=S7COMMPLUS was explicitly requested

S7Comm.zip

@Erik1000

Erik1000 commented Jul 7, 2026

Copy link
Copy Markdown
image image image

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>
@gijzelaerr

Copy link
Copy Markdown
Owner Author

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 (supported_versions, key_share, psk_key_exchange_modes) in the ClientHello even when the minimum version is set to TLS 1.2. The S7-1500 PLC can't parse these unknown extensions and drops the connection.

New fix (3bbce48): pin maximum_version = TLSv1_2 so the ClientHello is a clean TLS 1.2 hello without any TLS 1.3 extensions. This should bring the ClientHello down to ~300 bytes and be fully compatible with the PLC.

Could you test again with the latest commit?

@Erik1000

Erik1000 commented Jul 7, 2026

Copy link
Copy Markdown

nope:

DEBUG:snap7.connection:TCP connected to 172.25.250.129:102
DEBUG:snap7.connection:Sent COTP Connection Request
DEBUG:snap7.connection:Negotiated PDU size: 1024
DEBUG:snap7.connection:Unsupported COTP parameter: code=0xc1, length=2
DEBUG:snap7.connection:Unsupported COTP parameter: code=0xc2, length=16
DEBUG:snap7.connection:Received COTP Connection Confirm
INFO:snap7.connection:Connected to 172.25.250.129:102, PDU size: 1024
DEBUG:s7.connection:=== InitSSL === sending (26 bytes): 72 01 00 12 31 00 00 05 b3 00 00 00 00 00 00 00 00 30 00 00 00 00 72 01 00 00
DEBUG:snap7.connection:Sent 33 bytes: 03 00 00 21 02 f0 80 72 01 00 12 31 00 00 05 b3 00 00 00 00 00 00 00 00 30 00 00 00 00 72 01 00 00
DEBUG:snap7.connection:Received TPKT: version=3 length=31 payload (27 bytes): 02 f0 80 72 01 00 10 32 00 00 05 b3 00 00 00 00 70 00 00 00 00 00 00 72 01 00 00
DEBUG:s7.connection:=== InitSSL === received (24 bytes): 72 01 00 10 32 00 00 05 b3 00 00 00 00 70 00 00 00 00 00 00 72 01 00 00
DEBUG:s7.connection:InitSSL response: version=V1, data_length=16
DEBUG:s7.connection:InitSSL response body (20 bytes): 32 00 00 05 b3 00 00 00 00 70 00 00 00 00 00 00 72 01 00 00
DEBUG:snap7.connection:Sent 164 bytes: 03 00 00 a4 02 f0 80 16 03 01 00 98 01 00 00 94 03 03 cd 98 9c 5a 79 77 fb f7 4f 18 1f 2e d4 b5 ec 61 41 04 a2 4c a8 82 c3 53 f8 ce e9 c8 24 cc cf dc 00 00 1c c0 2c c0 30 c0 2b c0 2f cc a9 cc a8 c0 24 c0 28 c0 23 c0 27 00 9f 00 9e 00 6b 00 67 01 00 00 4f ff 01 00 01 00 00 0b 00 04 03 00 01 02 00 0a 00 04 00 02 00 17 00 23 00 00 00 16 00 00 00 17 00 00 00 0d 00 2a 00 28 04 03 05 03 06 03 08 07 08 08 08 09 08 0a 08 0b 08 04 08 05 08 06 04 01 05 01 06 01 03 03 03 01 03 02 04 02 05 02 06 02
INFO:snap7.connection:Disconnected from 172.25.250.129:102
DEBUG:s7.client:S7CommPlus connection failed: Receive error: [WinError 10054] An existing connection was forcibly closed by the remote host
Traceback (most recent call last):
  File "<frozen runpy>", line 203, in _run_module_as_main
  File "<frozen runpy>", line 88, in _run_code
  File "X\s7_comm\__main__.py", line 10, in <module>
    client.connect(
    ~~~~~~~~~~~~~~^
        "172.25.250.129",
        ^^^^^^^^^^^^^^^^^
    ...<3 lines>...
        protocol=Protocol.S7COMMPLUS,
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    )
    ^
  File "X\.venv\Lib\site-packages\s7\client.py", line 124, in 
connect
    raise RuntimeError(
        f"S7CommPlus connection to {address}:{tcp_port} failed and protocol=S7COMMPLUS was explicitly requested"
    )
RuntimeError: S7CommPlus connection to 172.25.250.129:102 failed and protocol=S7COMMPLUS was explicitly requested

S7Comm (2).zip

@gijzelaerr

Copy link
Copy Markdown
Owner Author

@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 session_ticket and encrypt_then_mac that the PLC's minimal TLS stack likely can't parse. TIA Portal sends a much more minimal ClientHello — just 2–4 ECDHE+AES-GCM cipher suites, no exotic extensions.

I've pushed a fix that:

  • Restricts cipher suites to ECDHE+AESGCM (4 suites instead of 14)
  • Disables session_ticket and encrypt_then_mac extensions
  • Keeps secp256r1 only for key exchange

This shrinks the ClientHello from 164 to 127 bytes. The one thing Python's ssl module can't control is the signature_algorithms list (20 algorithms) — there's no set_sigalgs() API until Python 3.15. But the PLC should just pick rsa_pkcs1_sha256 from the list and ignore the rest.

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>
@Erik1000

Erik1000 commented Jul 7, 2026

Copy link
Copy Markdown

I dont think the client hello succeeds yet?

DEBUG:snap7.connection:TCP connected to 172.25.250.129:102
DEBUG:snap7.connection:Sent COTP Connection Request
DEBUG:snap7.connection:Negotiated PDU size: 1024
DEBUG:snap7.connection:Unsupported COTP parameter: code=0xc1, length=2
DEBUG:snap7.connection:Unsupported COTP parameter: code=0xc2, length=16
DEBUG:snap7.connection:Received COTP Connection Confirm
INFO:snap7.connection:Connected to 172.25.250.129:102, PDU size: 1024
DEBUG:s7.connection:=== InitSSL === sending (26 bytes): 72 01 00 12 31 00 00 05 b3 00 00 00 00 00 00 00 00 30 00 00 00 00 72 01 00 00
DEBUG:snap7.connection:Sent 33 bytes: 03 00 00 21 02 f0 80 72 01 00 12 31 00 00 05 b3 00 00 00 00 00 00 00 00 30 00 00 00 00 72 01 00 00
DEBUG:snap7.connection:Received TPKT: version=3 length=31 payload (27 bytes): 02 f0 80 72 01 00 10 32 00 00 05 b3 00 00 00 00 70 00 00 00 00 00 00 72 01 00 00
DEBUG:s7.connection:=== InitSSL === received (24 bytes): 72 01 00 10 32 00 00 05 b3 00 00 00 00 70 00 00 00 00 00 00 72 01 00 00
DEBUG:s7.connection:InitSSL response: version=V1, data_length=16
DEBUG:s7.connection:InitSSL response body (20 bytes): 32 00 00 05 b3 00 00 00 00 70 00 00 00 00 00 00 72 01 00 00
DEBUG:snap7.connection:Sent 136 bytes: 03 00 00 88 02 f0 80 16 03 01 00 7c 01 00 00 78 03 03 10 b7 f8 1f eb 85 89 e8 00 b2 93 e0 bf 59 69 02 5a 01 e6 bf 14 30 f6 9f a8 c3 c5 be df 81 fa fd 00 00 08 c0 2c c0 30 c0 2b c0 2f 01 00 00 47 ff 01 00 01 00 00 0b 00 04 03 00 01 02 00 0a 00 04 00 02 00 17 00 17 00 00 00 0d 00 2a 00 28 04 03 05 03 06 03 08 07 08 08 08 09 08 0a 08 0b 08 04 08 05 08 06 04 01 05 01 06 01 03 03 03 01 03 02 04 02 05 02 06 02
INFO:snap7.connection:Disconnected from 172.25.250.129:102
DEBUG:s7.client:S7CommPlus connection failed: Receive error: [WinError 10054] An existing connection was forcibly closed by the remote host
Traceback (most recent call last):
  File "<frozen runpy>", line 203, in _run_module_as_main
  File "<frozen runpy>", line 88, in _run_code
  File "X\s7_comm\__main__.py", line 10, in <module>
    client.connect(
    ~~~~~~~~~~~~~~^
        "172.25.250.129",
        ^^^^^^^^^^^^^^^^^
    ...<3 lines>...
        protocol=Protocol.S7COMMPLUS,
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    )
    ^
  File "X\.venv\Lib\site-packages\s7\client.py", line 124, in connect
    raise RuntimeError(
        f"S7CommPlus connection to {address}:{tcp_port} failed and protocol=S7COMMPLUS was explicitly requested"
    )
RuntimeError: S7CommPlus connection to 172.25.250.129:102 failed and protocol=S7COMMPLUS was explicitly requested

S7Comm.zip

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>
@gijzelaerr

gijzelaerr commented Jul 7, 2026

Copy link
Copy Markdown
Owner Author

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:

  1. Adds RSA-only cipher suites as fallback (in case the PLC doesn't support ECDHE)
  2. Drops ECDSA cipher suites (PLCs use RSA certificates)

However, the signature algorithms issue can't be fixed from Python code — CPython's ssl module doesn't expose SSL_CTX_set1_sigalgs_list. The fix is to restrict them via OPENSSL_CONF before the process starts. Create a file s7-openssl.cnf:

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+SHA384

Then run with:

OPENSSL_CONF=/path/to/s7-openssl.cnf python your_script.py

Could 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>
@Erik1000

Erik1000 commented Jul 7, 2026

Copy link
Copy Markdown

this time in a container based on ghcr.io/astral-sh/uv:python3.14-bookworm-slim
due to the following error on windows:

DEBUG:s7.connection:Cannot restrict TLS sigalgs: libssl not found
s7-comm  | DEBUG:snap7.connection:TCP connected to 172.25.250.129:102
s7-comm  | DEBUG:snap7.connection:Sent COTP Connection Request
s7-comm  | DEBUG:snap7.connection:Negotiated PDU size: 1024
s7-comm  | DEBUG:snap7.connection:Unsupported COTP parameter: code=0xc1, length=2
s7-comm  | DEBUG:snap7.connection:Unsupported COTP parameter: code=0xc2, length=16
s7-comm  | DEBUG:snap7.connection:Received COTP Connection Confirm
s7-comm  | INFO:snap7.connection:Connected to 172.25.250.129:102, PDU size: 1024
s7-comm  | DEBUG:s7.connection:=== InitSSL === sending (26 bytes): 72 01 00 12 31 00 00 05 b3 00 00 00 00 00 00 00 00 30 00 00 00 00 72 01 00 00
s7-comm  | DEBUG:snap7.connection:Sent 33 bytes: 03 00 00 21 02 f0 80 72 01 00 12 31 00 00 05 b3 00 00 00 00 00 00 00 00 30 00 00 00 00 72 01 00 00
s7-comm  | DEBUG:snap7.connection:Received TPKT: version=3 length=31 payload (27 bytes): 02 f0 80 72 01 00 10 32 00 00 05 b3 00 00 00 00 70 00 00 00 00 00 00 72 01 00 00
s7-comm  | DEBUG:s7.connection:=== InitSSL === received (24 bytes): 72 01 00 10 32 00 00 05 b3 00 00 00 00 70 00 00 00 00 00 00 72 01 00 00
s7-comm  | DEBUG:s7.connection:InitSSL response: version=V1, data_length=16
s7-comm  | DEBUG:s7.connection:InitSSL response body (20 bytes): 32 00 00 05 b3 00 00 00 00 70 00 00 00 00 00 00 72 01 00 00
s7-comm  | DEBUG:s7.connection:TLS signature algorithms restricted to: RSA+SHA256:RSA+SHA384:RSA+SHA512:ECDSA+SHA256:ECDSA+SHA384
s7-comm  | DEBUG:snap7.connection:Sent 107 bytes: 03 00 00 6b 02 f0 80 16 03 01 00 5f 01 00 00 5b 03 03 ab fb 43 f3 fb 31 ed 2b 7e 25 1a b3 3f 03 ce 71 e6 3e 41 f4 4f 86 7b 5f 30 4d 97 33 31 2c e2 31 00 00 0e c0 2f c0 30 00 9c 00 9d 00 3c 00 3d 00 ff 01 00 00 24 00 0b 00 04 03 00 01 02 00 0a 00 04 00 02 00 17 00 17 00 00 00 0d 00 0c 00 0a 04 01 05 01 06 01 04 03 05 03
s7-comm  | INFO:snap7.connection:Disconnected from 172.25.250.129:102
s7-comm  | DEBUG:s7.client:S7CommPlus connection failed: Connection closed by peer
s7-comm  | Traceback (most recent call last):
s7-comm  |   File "<frozen runpy>", line 198, in _run_module_as_main
s7-comm  |   File "<frozen runpy>", line 88, in _run_code
s7-comm  |   File "/app/s7_comm/__main__.py", line 10, in <module>
s7-comm  |     client.connect(
s7-comm  |     ~~~~~~~~~~~~~~^
s7-comm  |         "172.25.250.129",
s7-comm  |         ^^^^^^^^^^^^^^^^^
s7-comm  |     ...<3 lines>...
s7-comm  |         protocol=Protocol.S7COMMPLUS,
s7-comm  |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
s7-comm  |     )
s7-comm  |     ^
s7-comm  |   File "/app/.venv/lib/python3.14/site-packages/s7/client.py", line 124, in connect
s7-comm  |     raise RuntimeError(
s7-comm  |         f"S7CommPlus connection to {address}:{tcp_port} failed and protocol=S7COMMPLUS was explicitly requested"
s7-comm  |     )
s7-comm  | RuntimeError: S7CommPlus connection to 172.25.250.129:102 failed and protocol=S7COMMPLUS was explicitly requested
s7-comm exited with code 1 (restarting)
s7-comm  | DEBUG:snap7.connection:TCP connected to 172.25.250.129:102
s7-comm  | DEBUG:snap7.connection:Sent COTP Connection Request
s7-comm  | DEBUG:snap7.connection:Negotiated PDU size: 1024
s7-comm  | DEBUG:snap7.connection:Unsupported COTP parameter: code=0xc1, length=2
s7-comm  | DEBUG:snap7.connection:Unsupported COTP parameter: code=0xc2, length=16
s7-comm  | DEBUG:snap7.connection:Received COTP Connection Confirm
s7-comm  | INFO:snap7.connection:Connected to 172.25.250.129:102, PDU size: 1024
s7-comm  | DEBUG:s7.connection:=== InitSSL === sending (26 bytes): 72 01 00 12 31 00 00 05 b3 00 00 00 00 00 00 00 00 30 00 00 00 00 72 01 00 00
s7-comm  | DEBUG:snap7.connection:Sent 33 bytes: 03 00 00 21 02 f0 80 72 01 00 12 31 00 00 05 b3 00 00 00 00 00 00 00 00 30 00 00 00 00 72 01 00 00
s7-comm  | DEBUG:snap7.connection:Received TPKT: version=3 length=31 payload (27 bytes): 02 f0 80 72 01 00 10 32 00 00 05 b3 00 00 00 00 70 00 00 00 00 00 00 72 01 00 00
s7-comm  | DEBUG:s7.connection:=== InitSSL === received (24 bytes): 72 01 00 10 32 00 00 05 b3 00 00 00 00 70 00 00 00 00 00 00 72 01 00 00
s7-comm  | DEBUG:s7.connection:InitSSL response: version=V1, data_length=16
s7-comm  | DEBUG:s7.connection:InitSSL response body (20 bytes): 32 00 00 05 b3 00 00 00 00 70 00 00 00 00 00 00 72 01 00 00
s7-comm  | DEBUG:s7.connection:TLS signature algorithms restricted to: RSA+SHA256:RSA+SHA384:RSA+SHA512:ECDSA+SHA256:ECDSA+SHA384
s7-comm  | DEBUG:snap7.connection:Sent 107 bytes: 03 00 00 6b 02 f0 80 16 03 01 00 5f 01 00 00 5b 03 03 d2 bc ee 20 95 47 c0 a0 c2 01 b3 38 80 47 88 5b e4 9d b8 f5 05 4c 40 32 46 cc 03 4b 68 7b 92 59 00 00 0e c0 2f c0 30 00 9c 00 9d 00 3c 00 3d 00 ff 01 00 00 24 00 0b 00 04 03 00 01 02 00 0a 00 04 00 02 00 17 00 17 00 00 00 0d 00 0c 00 0a 04 01 05 01 06 01 04 03 05 03
s7-comm  | INFO:snap7.connection:Disconnected from 172.25.250.129:102
s7-comm  | DEBUG:s7.client:S7CommPlus connection failed: Connection closed by peer
s7-comm  | Traceback (most recent call last):
s7-comm  |   File "<frozen runpy>", line 198, in _run_module_as_main
s7-comm  |   File "<frozen runpy>", line 88, in _run_code
s7-comm  |   File "/app/s7_comm/__main__.py", line 10, in <module>
s7-comm  |     client.connect(
s7-comm  |     ~~~~~~~~~~~~~~^
s7-comm  |         "172.25.250.129",
s7-comm  |         ^^^^^^^^^^^^^^^^^
s7-comm  |     ...<3 lines>...
s7-comm  |         protocol=Protocol.S7COMMPLUS,
s7-comm  |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
s7-comm  |     )
s7-comm  |     ^
s7-comm  |   File "/app/.venv/lib/python3.14/site-packages/s7/client.py", line 124, in connect
s7-comm  |     raise RuntimeError(
s7-comm  |         f"S7CommPlus connection to {address}:{tcp_port} failed and protocol=S7COMMPLUS was explicitly requested"
s7-comm  |     )
s7-comm  | RuntimeError: S7CommPlus connection to 172.25.250.129:102 failed and protocol=S7COMMPLUS was explicitly requested

S7Comm.zip

@Erik1000

Erik1000 commented Jul 7, 2026

Copy link
Copy Markdown

oh wait i didnt see your whole message

@Erik1000

Erik1000 commented Jul 7, 2026

Copy link
Copy Markdown

same error:

s7-comm exited with code 1 (restarting)
s7-comm  | DEBUG:snap7.connection:TCP connected to 172.25.250.129:102
s7-comm  | DEBUG:snap7.connection:Sent COTP Connection Request
s7-comm  | DEBUG:snap7.connection:Negotiated PDU size: 1024
s7-comm  | DEBUG:snap7.connection:Unsupported COTP parameter: code=0xc1, length=2
s7-comm  | DEBUG:snap7.connection:Unsupported COTP parameter: code=0xc2, length=16
s7-comm  | DEBUG:snap7.connection:Received COTP Connection Confirm
s7-comm  | INFO:snap7.connection:Connected to 172.25.250.129:102, PDU size: 1024
s7-comm  | DEBUG:s7.connection:=== InitSSL === sending (26 bytes): 72 01 00 12 31 00 00 05 b3 00 00 00 00 00 00 00 00 30 00 00 00 00 72 01 00 00
s7-comm  | DEBUG:snap7.connection:Sent 33 bytes: 03 00 00 21 02 f0 80 72 01 00 12 31 00 00 05 b3 00 00 00 00 00 00 00 00 30 00 00 00 00 72 01 00 00
s7-comm  | DEBUG:snap7.connection:Received TPKT: version=3 length=31 payload (27 bytes): 02 f0 80 72 01 00 10 32 00 00 05 b3 00 00 00 00 70 00 00 00 00 00 00 72 01 00 00
s7-comm  | DEBUG:s7.connection:=== InitSSL === received (24 bytes): 72 01 00 10 32 00 00 05 b3 00 00 00 00 70 00 00 00 00 00 00 72 01 00 00
s7-comm  | DEBUG:s7.connection:InitSSL response: version=V1, data_length=16
s7-comm  | DEBUG:s7.connection:InitSSL response body (20 bytes): 32 00 00 05 b3 00 00 00 00 70 00 00 00 00 00 00 72 01 00 00
s7-comm  | DEBUG:snap7.connection:Sent 107 bytes: 03 00 00 6b 02 f0 80 16 03 01 00 5f 01 00 00 5b 03 03 85 3b 46 3e fe d8 76 19 a1 6b d6 d2 48 5b a3 86 a7 c5 c4 89 a7 eb 5b 41 ba 74 57 c4 76 12 b1 a9 00 00 0e c0 2f c0 30 00 9c 00 9d 00 3c 00 3d 00 ff 01 00 00 24 00 0b 00 04 03 00 01 02 00 0a 00 04 00 02 00 17 00 17 00 00 00 0d 00 0c 00 0a 04 01 05 01 06 01 04 03 05 03
s7-comm  | INFO:snap7.connection:Disconnected from 172.25.250.129:102
s7-comm  | DEBUG:s7.client:S7CommPlus connection failed: Connection closed by peer
s7-comm  | Traceback (most recent call last):
s7-comm  |   File "<frozen runpy>", line 198, in _run_module_as_main
s7-comm  |   File "<frozen runpy>", line 88, in _run_code
s7-comm  |   File "/app/s7_comm/__main__.py", line 10, in <module>
s7-comm  |     client.connect(
s7-comm  |     ~~~~~~~~~~~~~~^
s7-comm  |         "172.25.250.129",
s7-comm  |         ^^^^^^^^^^^^^^^^^
s7-comm  |     ...<3 lines>...
s7-comm  |         protocol=Protocol.S7COMMPLUS,
s7-comm  |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
s7-comm  |     )
s7-comm  |     ^
s7-comm  |   File "/app/.venv/lib/python3.14/site-packages/s7/client.py", line 124, in connect
s7-comm  |     raise RuntimeError(
s7-comm  |         f"S7CommPlus connection to {address}:{tcp_port} failed and protocol=S7COMMPLUS was explicitly requested"
s7-comm  |     )
s7-comm  | RuntimeError: S7CommPlus connection to 172.25.250.129:102 failed and protocol=S7COMMPLUS was explicitly requested

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>
@gijzelaerr

Copy link
Copy Markdown
Owner Author

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:

Cipher suites (7): c02f, c030, 009c, 009d, 003c, 003d, 00ff(SCSV)  ✓
Extensions:
  ec_point_formats: 03 00 01 02  (uncompressed + compressed formats)
  supported_groups: secp256r1
  extended_master_secret: (empty)
  signature_algorithms: 5 algos  ✓

Pushed f2a9b69 — adds SSL_OP_NO_EXTENDED_MASTER_SECRET to strip the extended_master_secret extension.

If it still fails after that, the remaining suspect is ec_point_formats advertising compressed formats (01, 02) alongside uncompressed (00). TIA Portal only advertises uncompressed. Python's ssl gives us no API to control this, but we can eliminate the extension entirely by offering only RSA ciphers (no ECDHE).

To test that theory, you can temporarily override the cipher string in your s7-openssl.cnf:

[system_default_sect]
Groups = x25519:secp256r1:secp384r1
SignatureAlgorithms = RSA+SHA256:RSA+SHA384:RSA+SHA512:ECDSA+SHA256:ECDSA+SHA384
CipherString = AES128-GCM-SHA256:AES256-GCM-SHA384

This forces RSA-only ciphers, which removes ec_point_formats and supported_groups from the ClientHello entirely. If that connects, we know the compressed EC point formats are the problem.

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.

@Erik1000

Erik1000 commented Jul 7, 2026

Copy link
Copy Markdown
s7-comm  | DEBUG:snap7.connection:TCP connected to 172.25.250.129:102
s7-comm  | DEBUG:snap7.connection:Sent COTP Connection Request
s7-comm  | DEBUG:snap7.connection:Negotiated PDU size: 1024
s7-comm  | DEBUG:snap7.connection:Unsupported COTP parameter: code=0xc1, length=2
s7-comm  | DEBUG:snap7.connection:Unsupported COTP parameter: code=0xc2, length=16
s7-comm  | DEBUG:snap7.connection:Received COTP Connection Confirm
s7-comm  | INFO:snap7.connection:Connected to 172.25.250.129:102, PDU size: 1024
s7-comm  | DEBUG:s7.connection:=== InitSSL === sending (26 bytes): 72 01 00 12 31 00 00 05 b3 00 00 00 00 00 00 00 00 30 00 00 00 00 72 01 00 00
s7-comm  | DEBUG:snap7.connection:Sent 33 bytes: 03 00 00 21 02 f0 80 72 01 00 12 31 00 00 05 b3 00 00 00 00 00 00 00 00 30 00 00 00 00 72 01 00 00
s7-comm  | DEBUG:snap7.connection:Received TPKT: version=3 length=31 payload (27 bytes): 02 f0 80 72 01 00 10 32 00 00 05 b3 00 00 00 00 70 00 00 00 00 00 00 72 01 00 00
s7-comm  | DEBUG:s7.connection:=== InitSSL === received (24 bytes): 72 01 00 10 32 00 00 05 b3 00 00 00 00 70 00 00 00 00 00 00 72 01 00 00
s7-comm  | DEBUG:s7.connection:InitSSL response: version=V1, data_length=16
s7-comm  | DEBUG:s7.connection:InitSSL response body (20 bytes): 32 00 00 05 b3 00 00 00 00 70 00 00 00 00 00 00 72 01 00 00
s7-comm  | DEBUG:snap7.connection:Sent 103 bytes: 03 00 00 67 02 f0 80 16 03 01 00 5b 01 00 00 57 03 03 24 d3 00 d3 24 4b 7b 57 2c 76 48 7a 4e 12 93 77 0c 63 6d 48 86 3d fb 44 ff b3 f2 b0 b1 a9 e2 35 00 00 0e c0 2f c0 30 00 9c 00 9d 00 3c 00 3d 00 ff 01 00 00 20 00 0b 00 04 03 00 01 02 00 0a 00 04 00 02 00 17 00 0d 00 0c 00 0a 04 01 05 01 06 01 04 03 05 03
s7-comm  | INFO:snap7.connection:Disconnected from 172.25.250.129:102
s7-comm  | DEBUG:s7.client:S7CommPlus connection failed: Connection closed by peer
s7-comm  | Traceback (most recent call last):
s7-comm  |   File "<frozen runpy>", line 198, in _run_module_as_main
s7-comm  |   File "<frozen runpy>", line 88, in _run_code
s7-comm  |   File "/app/s7_comm/__main__.py", line 10, in <module>
s7-comm  |     client.connect(
s7-comm  |     ~~~~~~~~~~~~~~^
s7-comm  |         "172.25.250.129",
s7-comm  |         ^^^^^^^^^^^^^^^^^
s7-comm  |     ...<3 lines>...
s7-comm  |         protocol=Protocol.S7COMMPLUS,
s7-comm  |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
s7-comm  |     )
s7-comm  |     ^
s7-comm  |   File "/app/.venv/lib/python3.14/site-packages/s7/client.py", line 124, in connect
s7-comm  |     raise RuntimeError(
s7-comm  |         f"S7CommPlus connection to {address}:{tcp_port} failed and protocol=S7COMMPLUS was explicitly requested"
s7-comm  |     )
s7-comm  | RuntimeError: S7CommPlus connection to 172.25.250.129:102 failed and protocol=S7COMMPLUS was explicitly requested
image image

S7Comm.zip

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>
@gijzelaerr

Copy link
Copy Markdown
Owner Author

@Erik1000 Pushed 8fa3384 — switches to RSA-only cipher suites, dropping all ECDHE ciphers.

Why: 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 (01, 02) alongside uncompressed (00), and there's no API to suppress them. Your captures show the PLC sending a bare TCP RST after seeing these extensions.

What changes in the ClientHello:

Before (103 bytes) After (~71 bytes)
7 cipher suites (ECDHE + RSA) 5 cipher suites (RSA only)
ec_point_formats removed
supported_groups removed
signature_algorithms (5 algos) signature_algorithms (5 algos)

The only remaining extension should be signature_algorithms. No more OPENSSL_CONF workaround needed — just upgrade the package and test.

If it still RSTs, the last suspect is the ECDSA entries in signature_algorithms (the PLC might not tolerate unknown sigalg values). That would need an OPENSSL_CONF with SignatureAlgorithms = RSA+SHA256:RSA+SHA384:RSA+SHA512 to test the theory.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

S7-1512SP-1 PN always falls back to LEGACY

3 participants