Skip to content

Commit ebb7ac8

Browse files
authored
Solve test of tls by adding certificates and remove bugs (#1080)
* Solve tls.
1 parent ff6341f commit ebb7ac8

File tree

11 files changed

+145
-78
lines changed

11 files changed

+145
-78
lines changed

examples/certificates/pymodbus.crt

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIIDKjCCAhICCQCA+XBz2frVMDANBgkqhkiG9w0BAQsFADBXMQswCQYDVQQGEwJF
3+
UzEQMA4GA1UECAwHR3JhbmFkYTEQMA4GA1UEBwwHR3JhbmFkYTERMA8GA1UECgwI
4+
cHltb2RidXMxETAPBgNVBAMMCHB5bW9kYnVzMB4XDTIyMDkwODE1NDgzNVoXDTMy
5+
MDkwNTE1NDgzNVowVzELMAkGA1UEBhMCRVMxEDAOBgNVBAgMB0dyYW5hZGExEDAO
6+
BgNVBAcMB0dyYW5hZGExETAPBgNVBAoMCHB5bW9kYnVzMREwDwYDVQQDDAhweW1v
7+
ZGJ1czCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJMRjbCj1S64SgyR
8+
tMMMLWHf80xSYjCPcw9JXkAjAPp76fCV1b5dxc2BRvgwRm7xbfPHFUNLC8stQres
9+
hBBDUBbaXdmfwg5M4IwKeEekvOcFKbtBe2ZHFiwmSY4SR77jK7zVn/1WIwlSqIiI
10+
La9t4Ij79W/X0RWqTa3ExRHhB0JKNmn2REbNbhQ1RsLYM5fRsbUM51aChvpuHsS2
11+
myhPlXqk/37/M8VNLxJuhOz+S6iwSUsOdAwlnXJFm4CM9fWgq+jx5O4RPYMkYWIy
12+
UKVvUlKaPieZkrMd3AfDFcYZ/6rAwykVkl7ghCfuw33g1CSOxJQrNrxRX25omD6d
13+
nw+K158CAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAj5R0dBtHDnxyuAwgFgUVyMoT
14+
uf+lnO9tfeZ6GdcUPJSahzopDk7LBLLM5HHVDUoCMtEBdZ3p01IGSvVG11dfFatZ
15+
Ej7oYJGvm8P9z24n2LLve4IbEk42mb+hD8zY4DBD4WGMeleq8Zcbq7Y91s1+u9n8
16+
rMILX5WrmZkcvCzfSSvqdLN/XKsFGM9X0dxGuF/eXplyg7mb6U7CLWwG0wo84HnQ
17+
rIHw20Ss/BQklUpgihn4DHMOtuuTzNY9r1sYlDSFg8syMreNv25F303+mEOWMyBj
18+
r8eUiDlSJg6vsZ+CzbAeNhEj5muIXVjXy0Vj1toe5dVqdtCnRtz0bZCwsrn5Sg==
19+
-----END CERTIFICATE-----

examples/certificates/pymodbus.key

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
-----BEGIN PRIVATE KEY-----
2+
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCTEY2wo9UuuEoM
3+
kbTDDC1h3/NMUmIwj3MPSV5AIwD6e+nwldW+XcXNgUb4MEZu8W3zxxVDSwvLLUK3
4+
rIQQQ1AW2l3Zn8IOTOCMCnhHpLznBSm7QXtmRxYsJkmOEke+4yu81Z/9ViMJUqiI
5+
iC2vbeCI+/Vv19EVqk2txMUR4QdCSjZp9kRGzW4UNUbC2DOX0bG1DOdWgob6bh7E
6+
tpsoT5V6pP9+/zPFTS8SboTs/kuosElLDnQMJZ1yRZuAjPX1oKvo8eTuET2DJGFi
7+
MlClb1JSmj4nmZKzHdwHwxXGGf+qwMMpFZJe4IQn7sN94NQkjsSUKza8UV9uaJg+
8+
nZ8PitefAgMBAAECggEAJRU/AfQ9k9s6KLMwviG667v5cBwx0AbLv2YDku6Al6hZ
9+
E6XkUGz6rFfVmk0p5V2RGO/xB5fLsH6IHSIt5p/iePC0y2QCHXL7B7S4IXCDhUkC
10+
/cmEIQT5rUY2M9GeL56+b436Gnn2uaoS/uI/isWM4V2OxJXJjcn0bkV4X4q7NtZ3
11+
yZfjHIqMfOu7/nIsc27K1mF3WoWViUHVRMbc7mxU9J30xwNRfdtxuTVHnAa+knwe
12+
e99XDgtx+XF/MaUERYQwbv1JBKArIJgjS4+jZ3Bh7l7qv6htsVeNZCu6/xjkgivA
13+
I1Dy407JF8kVvxZSStKg955dfEjQmSKO2Iletq4OQQKBgQDCyTx2LXPfjpUFUJAb
14+
I0nLVZWtFae6xOhQ0YKNmbjFcOam38unG5+tZm5hQ22/H5zo6ybH9PeEEfqs9+dX
15+
C5TZ+1ybadmkilIfCSWpOGSVkS0irLunUhV3qpQNy40XREjqs8E04VwkmEMyBZMh
16+
1QSObKKvgw3kbLTxRT2aky6UYQKBgQDBSWOFX9uT88isvS5pc6jYtOHUdSvJjAXB
17+
8zOH3/Z7v6Xw0AeXfR/rSLbd0jWWih5QZyB3QgmtilB3ybF5IyQwcKWpaU4kStSF
18+
zaQOArnRjJoX5HiNTTcSlnRejIPGGcfDJ/5mEZdz7IZJejkx9bxgeU8Jd7itRyYH
19+
EUp29/Dr/wKBgQCE8osk8hMDhDLsRLeG/kfw08JsN7qavKj/+G/OLBy0DkIvpdI/
20+
hZgv5xjxo+81IuObCl0W33lNGGRrSG0KuWnoeisUHGQjbRFuA3VEEax9dXBEGXef
21+
VWQ3oCKbY9IyDjZikzwM1sBobdB3RNzOm7EXcMh9WMrTnOrHPoY0ib6iIQKBgGo7
22+
n8tKaDDlKEJop/2laAPTRtuymqJnpzJ2LLhdS+ev6dB1Rfbo+oYirP817eYTe53N
23+
UHa7gP40qw1reXOO8PD+uM5n5l7kEfKIl8ZrkR2vHXJMTEW9TquUrdjZegODX9vP
24+
O29187vvH896sbzXxvVvNxWJC6ORG0F/K2I/29aZAoGAEqflT9Tlg5NF06xoBXhK
25+
siiR9D9elgzUy/j3TVJbmPMniNGlHMj9JEVP8wnnMXoO8FBAF60YtetNWWsJVYCW
26+
c59f39IrIF/1vbE4YwTuEa3ldCQMF2wx6yFJr8nCrp/4KYJMvlpFWTgyueo7WD5u
27+
Uz5MX3quC+VnzfR5mwDPm1s=
28+
-----END PRIVATE KEY-----

examples/client_async.py

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
python3 server_sync.py
2222
"""
2323
import argparse
24+
import os
2425
import asyncio
2526
import logging
2627

@@ -49,6 +50,13 @@ def setup_async_client(args=None):
4950
if args.comm != "serial" and args.port:
5051
args.port = int(args.port)
5152
_logger.info("### Create client object")
53+
cwd = os.getcwd().split("/")[-1]
54+
if cwd == "examples":
55+
path = "."
56+
elif cwd == "test":
57+
path = "../examples"
58+
else:
59+
path = "examples"
5260

5361
if args.comm == "tcp":
5462
client = AsyncModbusTcpClient(
@@ -97,7 +105,7 @@ def setup_async_client(args=None):
97105
)
98106
elif args.comm == "tls":
99107
client = AsyncModbusTlsClient(
100-
"localhost",
108+
"127.0.0.1",
101109
port=args.port,
102110
# Common optional paramers:
103111
framer=args.framer,
@@ -107,11 +115,11 @@ def setup_async_client(args=None):
107115
# close_comm_on_error=False,
108116
# strict=True,
109117
# TLS setup parameters
110-
# sslctx=None,
111-
# certfile=None,
112-
# keyfile=None,
113-
# password=None,
114-
# server_hostname="localhost",
118+
# sslctx=sslctx,
119+
certfile=f"{path}/certificates/pymodbus.crt",
120+
keyfile=f"{path}/certificates/pymodbus.key",
121+
# password="none",
122+
server_hostname="localhost",
115123
)
116124
return client
117125

@@ -191,5 +199,5 @@ def get_commandline():
191199

192200
if __name__ == "__main__":
193201
# Connect/disconnect no calls.
194-
testclient = setup_async_client()
195-
asyncio.run(run_async_client(testclient))
202+
testclient = setup_async_client(".")
203+
asyncio.run(run_async_client(testclient), debug=True)

examples/client_sync.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
python3 server_sync.py
2222
"""
2323
import argparse
24+
import os
2425
import logging
2526

2627
# --------------------------------------------------------------------------- #
@@ -47,6 +48,13 @@ def setup_sync_client(args=None):
4748
args = get_commandline()
4849
if args.comm != "serial" and args.port:
4950
args.port = int(args.port)
51+
cwd = os.getcwd().split("/")[-1]
52+
if cwd == "examples":
53+
path = "."
54+
elif cwd == "test":
55+
path = "../examples"
56+
else:
57+
path = "examples"
5058
_logger.info("### Create client object")
5159
if args.comm == "tcp":
5260
client = ModbusTcpClient(
@@ -106,10 +114,10 @@ def setup_sync_client(args=None):
106114
# strict=True,
107115
# TLS setup parameters
108116
# sslctx=None,
109-
# certfile=None,
110-
# keyfile=None,
117+
certfile=f"{path}/certificates/pymodbus.crt",
118+
keyfile=f"{path}/certificates/pymodbus.key",
111119
# password=None,
112-
# server_hostname="localhost",
120+
server_hostname="localhost",
113121
)
114122
return client
115123

examples/server_async.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
python3 client_sync.py
2929
"""
3030
import argparse
31+
import os
3132
import asyncio
3233
import logging
3334

@@ -145,6 +146,13 @@ def setup_async_server(args):
145146
async def run_async_server(args=None):
146147
"""Run server."""
147148
server_id, port, store, identity, framer = setup_async_server(args)
149+
cwd = os.getcwd().split("/")[-1]
150+
if cwd == "examples":
151+
path = "."
152+
elif cwd == "test":
153+
path = "../examples"
154+
else:
155+
path = "examples"
148156

149157
txt = f"### start ASYNC server on port {port}"
150158
_logger.info(txt)
@@ -215,10 +223,10 @@ async def run_async_server(args=None):
215223
framer=framer, # The framer strategy to use
216224
# handler=None, # handler for each session
217225
allow_reuse_address=True, # allow the reuse of an address
218-
# certfile=None, # The cert file path for TLS (used if sslctx is None)
219-
# sslctx=None, # The SSLContext to use for TLS (default None and auto create)
220-
# keyfile=None, # The key file path for TLS (used if sslctx is None)
221-
# password=None, # The password for for decrypting the private key file
226+
certfile=f"{path}/certificates/pymodbus.crt", # The cert file path for TLS (used if sslctx is None)
227+
# sslctx=sslctx, # The SSLContext to use for TLS (default None and auto create)
228+
keyfile=f"{path}/certificates/pymodbus.key", # The key file path for TLS (used if sslctx is None)
229+
# password="none", # The password for for decrypting the private key file
222230
# reqclicert=False, # Force the sever request client"s certificate
223231
# ignore_missing_slaves=True, # ignore request to a missing slave
224232
# broadcast_enable=False, # treat unit_id 0 as broadcast address,
@@ -305,4 +313,4 @@ def get_commandline():
305313

306314

307315
if __name__ == "__main__":
308-
asyncio.run(run_async_server())
316+
asyncio.run(run_async_server("."), debug=True)

examples/server_sync.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
python3 client_sync.py
2929
"""
3030
import argparse
31+
import os
3132
import logging
3233

3334
from pymodbus.datastore import (
@@ -144,6 +145,13 @@ def setup_sync_server(args):
144145
def run_sync_server(args=None):
145146
"""Run server."""
146147
server_id, port, store, identity, framer = setup_sync_server(args)
148+
cwd = os.getcwd().split("/")[-1]
149+
if cwd == "examples":
150+
path = "."
151+
elif cwd == "test":
152+
path = "../examples"
153+
else:
154+
path = "examples"
147155
txt = f"### start server, listening on {port} - {server_id}"
148156
_logger.info(txt)
149157
if server_id == "tcp":
@@ -214,10 +222,10 @@ def run_sync_server(args=None):
214222
address=None, # listen address
215223
framer=framer, # The framer strategy to use
216224
# handler=None, # handler for each session
217-
# allow_reuse_address=True, # allow the reuse of an address
218-
# certfile=None, # The cert file path for TLS (used if sslctx is None)
225+
allow_reuse_address=True, # allow the reuse of an address
226+
certfile=f"{path}/certificates/pymodbus.crt", # The cert file path for TLS (used if sslctx is None)
219227
# sslctx=None, # The SSLContext to use for TLS (default None and auto create)
220-
# keyfile=None, # The key file path for TLS (used if sslctx is None)
228+
keyfile=f"{path}/certificates/pymodbus.key", # The key file path for TLS (used if sslctx is None)
221229
# password=None, # The password for for decrypting the private key file
222230
# reqclicert=False, # Force the sever request client"s certificate
223231
# ignore_missing_slaves=True, # ignore request to a missing slave
@@ -305,5 +313,5 @@ def get_commandline():
305313

306314

307315
if __name__ == "__main__":
308-
server = run_sync_server()
316+
server = run_sync_server(".")
309317
server.shutdown()

pymodbus/client/tls.py

Lines changed: 15 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,9 @@
55
import ssl
66

77
from pymodbus.client.tcp import AsyncModbusTcpClient, ModbusTcpClient
8-
from pymodbus.client.base import ModbusClientProtocol
98
from pymodbus.constants import Defaults
109
from pymodbus.framer import ModbusFramer
1110
from pymodbus.framer.tls_framer import ModbusTlsFramer
12-
from pymodbus.transaction import FifoTransactionManager
1311

1412
_logger = logging.getLogger(__name__)
1513

@@ -28,18 +26,20 @@ def sslctx_provider(
2826
:param keyfile: The optional client"s key file path for TLS server request
2927
:param password: The password for for decrypting client"s private key file
3028
"""
31-
if sslctx is None:
32-
# According to MODBUS/TCP Security Protocol Specification, it is
33-
# TLSv2 at least
34-
sslctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
35-
sslctx.verify_mode = ssl.CERT_REQUIRED
36-
sslctx.check_hostname = True
37-
38-
if certfile and keyfile:
39-
sslctx.load_cert_chain(
40-
certfile=certfile, keyfile=keyfile, password=password
41-
)
42-
29+
if sslctx:
30+
return sslctx
31+
32+
sslctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
33+
sslctx.check_hostname = False
34+
sslctx.verify_mode = ssl.CERT_NONE
35+
sslctx.options |= ssl.OP_NO_TLSv1_1
36+
sslctx.options |= ssl.OP_NO_TLSv1
37+
sslctx.options |= ssl.OP_NO_SSLv3
38+
sslctx.options |= ssl.OP_NO_SSLv2
39+
if certfile and keyfile:
40+
sslctx.load_cert_chain(
41+
certfile=certfile, keyfile=keyfile, password=password
42+
)
4343
return sslctx
4444

4545

@@ -89,28 +89,11 @@ def __init__(
8989
self.params.keyfile = keyfile
9090
self.params.password = password
9191
self.params.server_hostname = server_hostname
92-
93-
if not sslctx:
94-
self.params.sslctx = ssl.create_default_context()
95-
# According to MODBUS/TCP Security Protocol Specification, it is
96-
# TLSv2 at least
97-
self.sslctx.options |= ssl.OP_NO_TLSv1_1
98-
self.sslctx.options |= ssl.OP_NO_TLSv1
99-
self.sslctx.options |= ssl.OP_NO_SSLv3
100-
self.sslctx.options |= ssl.OP_NO_SSLv2
101-
else:
102-
self.sslctx = sslctx
10392
AsyncModbusTcpClient.__init__(self, host, port=port, framer=framer, **kwargs)
10493

105-
async def connect(self):
106-
"""Initiate connection to start client."""
107-
# get current loop, if there are no loop a RuntimeError will be raised
108-
self.loop = asyncio.get_running_loop()
109-
return await AsyncModbusTcpClient.connect(self)
110-
11194
async def _connect(self):
11295
"""Connect to server."""
113-
_logger.debug("Connecting.")
96+
_logger.debug("Connecting tls.")
11497
try:
11598
return await self.loop.create_connection(
11699
self._create_protocol,
@@ -128,13 +111,6 @@ async def _connect(self):
128111
_logger.info(txt)
129112
self.reset_delay()
130113

131-
def _create_protocol(self):
132-
"""Create initialized protocol instance with Factory function."""
133-
protocol = ModbusClientProtocol(framer=self.params.framer, **self.params.kwargs)
134-
protocol.transaction = FifoTransactionManager(self)
135-
protocol.factory = self
136-
return protocol
137-
138114

139115
class ModbusTlsClient(ModbusTcpClient):
140116
"""**ModbusTlsClient**.

pymodbus/server/async_io.py

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,13 @@ def sslctx_provider(
5353
if sslctx is None:
5454
# According to MODBUS/TCP Security Protocol Specification, it is
5555
# TLSv2 at least
56-
sslctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
56+
sslctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
57+
sslctx.verify_mode = ssl.CERT_NONE
58+
sslctx.check_hostname = False
59+
sslctx.options |= ssl.OP_NO_TLSv1_1
60+
sslctx.options |= ssl.OP_NO_TLSv1
61+
sslctx.options |= ssl.OP_NO_SSLv3
62+
sslctx.options |= ssl.OP_NO_SSLv2
5763
sslctx.load_cert_chain(certfile=certfile, keyfile=keyfile, password=password)
5864

5965
if reqclicert:
@@ -542,7 +548,10 @@ async def serve_forever(self):
542548
**self.factory_parms,
543549
)
544550
self.serving.set_result(True)
545-
await self.server.serve_forever()
551+
try:
552+
await self.server.serve_forever()
553+
except asyncio.exceptions.CancelledError:
554+
pass
546555
else:
547556
raise RuntimeError(
548557
"Can't call serve_forever on an already running server object"
@@ -710,10 +719,13 @@ def __init__(
710719
async def serve_forever(self):
711720
"""Start endless loop."""
712721
if self.protocol is None:
713-
self.protocol, self.endpoint = await self.loop.create_datagram_endpoint(
714-
lambda: self.handler(self),
715-
**self.factory_parms,
716-
)
722+
try:
723+
self.protocol, self.endpoint = await self.loop.create_datagram_endpoint(
724+
lambda: self.handler(self),
725+
**self.factory_parms,
726+
)
727+
except asyncio.exceptions.CancelledError:
728+
pass
717729
self.serving.set_result(True)
718730
await self.stop_serving
719731
else:

pymodbus/transaction.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -526,7 +526,10 @@ def getTransaction(self, tid):
526526
"""
527527
txt = f"Getting transaction {tid}"
528528
_logger.debug(txt)
529-
529+
if not tid:
530+
if self.transactions:
531+
return self.transactions.popitem()[1]
532+
return None
530533
return self.transactions.pop(tid, None)
531534

532535
def delTransaction(self, tid):

0 commit comments

Comments
 (0)