Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Modbus Identity (TLS) Verification #606

Closed
sfl0r3nz05 opened this issue Feb 22, 2021 · 9 comments
Closed

Modbus Identity (TLS) Verification #606

sfl0r3nz05 opened this issue Feb 22, 2021 · 9 comments

Comments

@sfl0r3nz05
Copy link

Pymodbus Specific

  • Server: TLS - sync
  • Client: TLS - sync

Description

Hello Modbus experts,

I am trying to provide a mechanism for identity verification. For this, I need to retrieve the server and client certificates, as they are exchanged (see figure below). It is not clear to me how to retrieve these certificates from the current TLS implementation of pymodbus.

I would appreciate any hints.

Best,

Santiago.
image

@sfl0r3nz05
Copy link
Author

Versions

  • Python: 3.8
  • OS: Ubuntu Server 18.04
  • Pymodbus: latest

Pymodbus Specific

  • Server: tls
  • Client: tls

Description

As I have indicated in the previous issue, I am trying to verify the identities of both client and server before establishing the Modbus connection. For this purpose, I have modified the sync client to manage certificates and as part of, def connect method, I have enabled do_hanshake (see code below).

 # code and logs here.
self.sslctx = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)
self.sslctx.load_cert_chain(certfile=certfile, keyfile=keyfile)

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(self.source_address)
self.socket = self.sslctx.wrap_socket(sock, server_side=False, server_hostname=self.host, do_handshake_on_connect=False)
self.socket.settimeout(self.timeout)
self.socket.connect((self.host, self.port))
self.socket.do_handshake()
cert = self.socket.getpeercert(binary_form=True)
x509 = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_ASN1, cert)
pubKeyObject = x509.get_pubkey()
pubKeyString = OpenSSL.crypto.dump_publickey(OpenSSL.crypto.FILETYPE_PEM, pubKeyObject)
_logger.debug(pubKeyString)

This screenshoot shows how with client modified and server without modification, I am able to retrieve the public key to verification.
image

However, the server does not allow do_handshake:
image

I get next error:
image

I would appreciate any help,

Santiago.

@dhoomakethu
Copy link
Contributor

@starnight Would you mind taking a look in to this please ?

@sfl0r3nz05
Copy link
Author

Thanks @dhoomakethu, while a solution is found, I'm doing a trick that consists of taking the client's cert directly from the CA and extracting from the cert the public key to send to the external identity verification.

image

@starnight
Copy link
Contributor

starnight commented Feb 28, 2021

@sfl0r3nz05 Seems you found a solution.

However, I have a question for doing Server Identity Verification "again".

I believe the TLS handshake provided by Python's ssl library has already done "client verifies server's certificate" action. The 4th step mentioned in Table 5 TLS Full Handshake Protocol of MB-TCP-Security-v21_2018-07-24. Actually, the TLS used by MODBUS is RFC 5246 TLS The Transport Layer Security (TLS) Protocol.

Is there any reason leads you verifying the server's certificate on client side again?

@sfl0r3nz05
Copy link
Author

@starnight, I appreciate your comments.

Before answering your question, I should mention that the solution found is partial, because I am not receiving the certificate from the client (on the server side) but retrieving it directly from the CA. Actually, I would need to retrieve it in the red square in the figure. So, I would appreciate if you can recommend me how to implement do_handshake() in the server sync.

image

The reason is that external identity verification for me is hyperledger fabric blockchain (hfb). The identity of each Modbus device is based on the public key, so when the Modbus device, e.g. server, receives the certificate from the other party, e.g. client, it retrieves the public key and verifies the identity of this device (client) in hfb.

@starnight
Copy link
Contributor

Okay! Blockchain is an interesting usage. What you mentioned is more like the 8th step ClientCertificate and 9th step VerifyClientCertSig in Table 5 TLS Full Handshake Protocol of MB-TCP-Security-v21_2018-07-24.
This is an interesting part that is not done by Python's ssl library by default. It is also the same behavior of HTTPS (the S is the SSL/TLS). Besides, RFC 5246 TLS The Transport Layer Security (TLS) Protocol also mentioned:

When a TLS client and server first start communicating, they
agree on a protocol version, select cryptographic algorithms,
optionally authenticate each other, and use public-key encryption
techniques to generate shared secrets.

The optionally authenticate each other.
So, I guess that's why it is not done by Python's ssl by default.

According to the description of ssl.CERT_NONE:

  1. Except for PROTOCOL_TLS_CLIENT, it is the default mode.
  2. In server mode, no certificate is requested from the client, so the client does not send any for client cert authentication.

Here is why you do not see the client's certificate on server side.

Goes back to the 8th step ClientCertificate in Table 5 TLS Full Handshake Protocol of MB-TCP-Security-v21_2018-07-24:

The TlsClient sends its certificate chain as the payload of a
Certificate message.  This chain contains the client device’s
domain certificate, as well as the certificate for each issuing
CA down to the root CA. This client’s end certificate also
contains the role of the client.  This is used by the server to
authorize a later application level request.

Here is what you want, I guess. But, this has not implemented in pymodbus' MBAPS yet, due to the default behavior of Python's ssl library.

@sfl0r3nz05
Copy link
Author

Thanks for the response @starnight, this is exactly what happens and what I need. I will close the issue and come back when I find a solution.

@starnight
Copy link
Contributor

starnight commented Mar 2, 2021

But, this has not implemented in pymodbus' MBAPS yet, due to the default behavior of Python's ssl library.

Verification is okay, but I have not implement this feature yet. Because I have not figured out how to implement the authorization action in pymodbus.

Since we know this is due to Python's ssl library, we can simplify this problem into simple TCP server/client socket over TLS.
I prepare a Server/Client pair as the attachments for example. Please modify the filename extension to .py
server.txt
client.txt

You also have to prepare the server/client's key/cert pair, of course.

As you can see,

  • context.verify_mode = CERT_REQUIRED (ssl.CERT_REQUIRED) in server.txt is the key to ask client send client's certificate. Then, you can invoke connection.getpeercert() to get client's certificate after TLS handshake.
  • client.txt has to load client's certificate by invoking context.load_cert_chain(certfile='client-cert.pem', keyfile='client-key.pem') to be sent during TLS handshake.

These are all about the class of SSL Contexts.

I think context.verify_mode = CERT_REQUIRED fullfills the 8th step ClientCertificate and 9th step VerifyClientCertSig in Table 5 TLS Full Handshake Protocol of MB-TCP-Security-v21_2018-07-24. Just misses the authorization action (optional), or it could be seen as authorized if the client certificate is legal.

@starnight
Copy link
Contributor

starnight commented Mar 2, 2021

If someone figures out how to implement the authorization, pull request is welcomed! :)

starnight added a commit to starnight/pymodbus that referenced this issue Mar 7, 2021
This patch adds server requiring client's certificate feature which is
mentioned in the 6th step CertificateRequest to 9th step
VerifyClientCertSig in Table 5 TLS Full Handshake Protocol of MODBUS/TCP
Security Protocol Specification [1],

This feature is implemented with an optional argument "reqclicert" of
StartTlsServer() in both sync and async_io. So, users can force server
require client's certificate, or according to the SSL Context's original
behavior [2].

This fixes part of pymodbus-dev#606

[1]: http://modbus.org/docs/MB-TCP-Security-v21_2018-07-24.pdf
[2]: https://docs.python.org/3/library/ssl.html#ssl.SSLContext.verify_mode
starnight added a commit to starnight/pymodbus that referenced this issue Mar 7, 2021
This patch adds server requiring client's certificate feature which is
mentioned in the 6th step CertificateRequest to 9th step
VerifyClientCertSig in Table 5 TLS Full Handshake Protocol of MODBUS/TCP
Security Protocol Specification [1],

This feature is implemented with an optional argument "reqclicert" of
StartTlsServer() in both sync and async_io. So, users can force server
require client's certificate, or according to the SSL Context's original
behavior [2].

This fixes part of pymodbus-dev#606

[1]: http://modbus.org/docs/MB-TCP-Security-v21_2018-07-24.pdf
[2]: https://docs.python.org/3/library/ssl.html#ssl.SSLContext.verify_mode
starnight added a commit to starnight/pymodbus that referenced this issue Mar 7, 2021
This patch adds server requiring client's certificate feature which is
mentioned in the 6th step CertificateRequest to 9th step
VerifyClientCertSig in Table 5 TLS Full Handshake Protocol of MODBUS/TCP
Security Protocol Specification [1],

This feature is implemented with an optional argument "reqclicert" of
StartTlsServer() in both sync and async_io. So, users can force server
require client's certificate, or according to the SSL Context's original
behavior [2].

This fixes part of pymodbus-dev#606

[1]: http://modbus.org/docs/MB-TCP-Security-v21_2018-07-24.pdf
[2]: https://docs.python.org/3/library/ssl.html#ssl.SSLContext.verify_mode
starnight added a commit to starnight/pymodbus that referenced this issue Mar 7, 2021
This patch adds server requiring client's certificate feature which is
mentioned in the 6th step CertificateRequest to 9th step
VerifyClientCertSig in Table 5 TLS Full Handshake Protocol of MODBUS/TCP
Security Protocol Specification [1],

This feature is implemented with an optional argument "reqclicert" of
StartTlsServer() in both sync and async_io. So, users can force server
require client's certificate, or according to the SSL Context's original
behavior [2].

This fixes part of pymodbus-dev#606

[1]: http://modbus.org/docs/MB-TCP-Security-v21_2018-07-24.pdf
[2]: https://docs.python.org/3/library/ssl.html#ssl.SSLContext.verify_mode
starnight added a commit to starnight/pymodbus that referenced this issue Mar 7, 2021
This patch adds server requiring client's certificate feature which is
mentioned in the 6th step CertificateRequest to 9th step
VerifyClientCertSig in Table 5 TLS Full Handshake Protocol of MODBUS/TCP
Security Protocol Specification [1],

This feature is implemented with an optional argument "reqclicert" of
StartTlsServer() in both sync and async_io. So, users can force server
require client's certificate, or according to the SSL Context's original
behavior [2].

This fixes part of pymodbus-dev#606

[1]: http://modbus.org/docs/MB-TCP-Security-v21_2018-07-24.pdf
[2]: https://docs.python.org/3/library/ssl.html#ssl.SSLContext.verify_mode
starnight added a commit to starnight/pymodbus that referenced this issue Mar 10, 2021
This patch adds server requiring client's certificate feature which is
mentioned in the 6th step CertificateRequest to 9th step
VerifyClientCertSig in Table 5 TLS Full Handshake Protocol of MODBUS/TCP
Security Protocol Specification [1].

This patch implements the feature within both sync and async_io version.

* Server side:
Add an optional argument "reqclicert" of StartTlsServer(). So, users can
force server require client's certificate for TLS full handshake, or
according to the SSL Context's original behavior [2].

* Client side:
Add optional arguments "certfile" and "keyfile" for replying, if the
server requires client's certificate for TLS full handshake.

Besides, also add an optional argument "password" on both server and
client side for decrypting the private keyfile.

This fixes part of pymodbus-dev#606

[1]: http://modbus.org/docs/MB-TCP-Security-v21_2018-07-24.pdf
[2]: https://docs.python.org/3/library/ssl.html#ssl.SSLContext.verify_mode
starnight added a commit to starnight/pymodbus that referenced this issue Mar 10, 2021
This patch adds server requiring client's certificate feature which is
mentioned in the 6th step CertificateRequest to 9th step
VerifyClientCertSig in Table 5 TLS Full Handshake Protocol of MODBUS/TCP
Security Protocol Specification [1].

This patch implements the feature within both sync and async_io version.

* Server side:
Add an optional argument "reqclicert" of StartTlsServer(). So, users can
force server require client's certificate for TLS full handshake, or
according to the SSL Context's original behavior [2].

* Client side:
Add optional arguments "certfile" and "keyfile" for replying, if the
server requires client's certificate for TLS full handshake.

Besides, also add an optional argument "password" on both server and
client side for decrypting the private keyfile.

This fixes part of pymodbus-dev#606

[1]: http://modbus.org/docs/MB-TCP-Security-v21_2018-07-24.pdf
[2]: https://docs.python.org/3/library/ssl.html#ssl.SSLContext.verify_mode
starnight added a commit to starnight/pymodbus that referenced this issue Mar 10, 2021
This patch adds server requiring client's certificate feature which is
mentioned in the 6th step CertificateRequest to 9th step
VerifyClientCertSig in Table 5 TLS Full Handshake Protocol of MODBUS/TCP
Security Protocol Specification [1].

This patch implements the feature within both sync and async_io version.

* Server side:
Add an optional argument "reqclicert" of StartTlsServer(). So, users can
force server require client's certificate for TLS full handshake, or
according to the SSL Context's original behavior [2].

* Client side:
Add optional arguments "certfile" and "keyfile" for replying, if the
server requires client's certificate for TLS full handshake.

Besides, also add an optional argument "password" on both server and
client side for decrypting the private keyfile.

This fixes part of pymodbus-dev#606

[1]: http://modbus.org/docs/MB-TCP-Security-v21_2018-07-24.pdf
[2]: https://docs.python.org/3/library/ssl.html#ssl.SSLContext.verify_mode
starnight added a commit to starnight/pymodbus that referenced this issue Mar 10, 2021
This patch adds server requiring client's certificate feature which is
mentioned in the 6th step CertificateRequest to 9th step
VerifyClientCertSig in Table 5 TLS Full Handshake Protocol of MODBUS/TCP
Security Protocol Specification [1].

This patch implements the feature within both sync and async_io version.

* Server side:
Add an optional argument "reqclicert" of StartTlsServer(). So, users can
force server require client's certificate for TLS full handshake, or
according to the SSL Context's original behavior [2].

* Client side:
Add optional arguments "certfile" and "keyfile" for replying, if the
server requires client's certificate for TLS full handshake.

Besides, also add an optional argument "password" on both server and
client side for decrypting the private keyfile.

This fixes part of pymodbus-dev#606

[1]: http://modbus.org/docs/MB-TCP-Security-v21_2018-07-24.pdf
[2]: https://docs.python.org/3/library/ssl.html#ssl.SSLContext.verify_mode
starnight added a commit to starnight/pymodbus that referenced this issue Mar 10, 2021
This patch adds server requiring client's certificate feature which is
mentioned in the 6th step CertificateRequest to 9th step
VerifyClientCertSig in Table 5 TLS Full Handshake Protocol of MODBUS/TCP
Security Protocol Specification [1].

This patch implements the feature within both sync and async_io version.

* Server side:
Add an optional argument "reqclicert" of StartTlsServer(). So, users can
force server require client's certificate for TLS full handshake, or
according to the SSL Context's original behavior [2].

* Client side:
Add optional arguments "certfile" and "keyfile" for replying, if the
server requires client's certificate for TLS full handshake.

Besides, also add an optional argument "password" on both server and
client side for decrypting the private keyfile.

This fixes part of pymodbus-dev#606

[1]: http://modbus.org/docs/MB-TCP-Security-v21_2018-07-24.pdf
[2]: https://docs.python.org/3/library/ssl.html#ssl.SSLContext.verify_mode
starnight added a commit to starnight/pymodbus that referenced this issue Mar 10, 2021
This patch adds server requiring client's certificate feature which is
mentioned in the 6th step CertificateRequest to 9th step
VerifyClientCertSig in Table 5 TLS Full Handshake Protocol of MODBUS/TCP
Security Protocol Specification [1].

This patch implements the feature within both sync and async_io version.

* Server side:
Add an optional argument "reqclicert" of StartTlsServer(). So, users can
force server require client's certificate for TLS full handshake, or
according to the SSL Context's original behavior [2].

* Client side:
Add optional arguments "certfile" and "keyfile" for replying, if the
server requires client's certificate for TLS full handshake.

Besides, also add an optional argument "password" on both server and
client side for decrypting the private keyfile.

This fixes part of pymodbus-dev#606

[1]: http://modbus.org/docs/MB-TCP-Security-v21_2018-07-24.pdf
[2]: https://docs.python.org/3/library/ssl.html#ssl.SSLContext.verify_mode
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Apr 21, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants