opcua-asyncio icon indicating copy to clipboard operation
opcua-asyncio copied to clipboard

Can not connect to kepserver using client_set_security_string

Open nieyanzhai opened this issue 3 years ago • 7 comments

kepserver opcua config:

image

python code:

image

error message:

C:\Users\yanzh\Desktop\codes\connect_to_opcua_security\venv\Scripts\python.exe C:/Users/yanzh/Desktop/codes/connect_to_opcua_security/main.py Exception raised while parsing message from server Traceback (most recent call last): File "C:\Users\yanzh\Desktop\codes\connect_to_opcua_security\venv\lib\site-packages\asyncua\client\ua_client.py", line 78, in _process_received_data msg = self._connection.receive_from_header_and_body(header, buf) File "C:\Users\yanzh\Desktop\codes\connect_to_opcua_security\venv\lib\site-packages\asyncua\common\connection.py", line 349, in receive_from_header_and_body chunk = MessageChunk.from_header_and_body(self.security_policy, header, body, use_prev_key=False) File "C:\Users\yanzh\Desktop\codes\connect_to_opcua_security\venv\lib\site-packages\asyncua\common\connection.py", line 60, in from_header_and_body decrypted = crypto.decrypt(data.read(len(data))) File "C:\Users\yanzh\Desktop\codes\connect_to_opcua_security\venv\lib\site-packages\asyncua\crypto\security_policies.py", line 198, in decrypt return self.Decryptor.decrypt(data) File "C:\Users\yanzh\Desktop\codes\connect_to_opcua_security\venv\lib\site-packages\asyncua\crypto\security_policies.py", line 309, in decrypt decrypted += self.decryptor(self.client_pk, File "C:\Users\yanzh\Desktop\codes\connect_to_opcua_security\venv\lib\site-packages\asyncua\crypto\uacrypto.py", line 123, in decrypt_rsa_oaep text = private_key.decrypt( File "C:\Users\yanzh\Desktop\codes\connect_to_opcua_security\venv\lib\site-packages\cryptography\hazmat\backends\openssl\rsa.py", line 433, in decrypt return _enc_dec_rsa(self._backend, self, ciphertext, padding) File "C:\Users\yanzh\Desktop\codes\connect_to_opcua_security\venv\lib\site-packages\cryptography\hazmat\backends\openssl\rsa.py", line 87, in _enc_dec_rsa return _enc_dec_rsa_pkey_ctx(backend, key, data, padding_enum, padding) File "C:\Users\yanzh\Desktop\codes\connect_to_opcua_security\venv\lib\site-packages\cryptography\hazmat\backends\openssl\rsa.py", line 147, in _enc_dec_rsa_pkey_ctx res = crypt(pkey_ctx, buf, outlen, data, len(data)) TypeError: initializer for ctype 'unsigned char *' must be a cdata pointer, not bytearray disconnect_socket was called but connection is closed Traceback (most recent call last): File "C:\Program Files\Python310\lib\asyncio\tasks.py", line 456, in wait_for return fut.result() asyncio.exceptions.CancelledError

The above exception was the direct cause of the following exception:

Traceback (most recent call last): File "C:\Users\yanzh\Desktop\codes\connect_to_opcua_security\main.py", line 20, in asyncio.run(main()) File "C:\Program Files\Python310\lib\asyncio\runners.py", line 44, in run return loop.run_until_complete(main) File "C:\Program Files\Python310\lib\asyncio\base_events.py", line 641, in run_until_complete return future.result() File "C:\Users\yanzh\Desktop\codes\connect_to_opcua_security\main.py", line 14, in main async with client: File "C:\Users\yanzh\Desktop\codes\connect_to_opcua_security\venv\lib\site-packages\asyncua\client\client.py", line 68, in aenter await self.connect() File "C:\Users\yanzh\Desktop\codes\connect_to_opcua_security\venv\lib\site-packages\asyncua\client\client.py", line 246, in connect await self.open_secure_channel() File "C:\Users\yanzh\Desktop\codes\connect_to_opcua_security\venv\lib\site-packages\asyncua\client\client.py", line 297, in open_secure_channel result = await self.uaclient.open_secure_channel(params) File "C:\Users\yanzh\Desktop\codes\connect_to_opcua_security\venv\lib\site-packages\asyncua\client\ua_client.py", line 280, in open_secure_channel return await self.protocol.open_secure_channel(params) File "C:\Users\yanzh\Desktop\codes\connect_to_opcua_security\venv\lib\site-packages\asyncua\client\ua_client.py", line 213, in open_secure_channel await asyncio.wait_for(self._send_request(request, message_type=ua.MessageType.SecureOpen), self.timeout) File "C:\Program Files\Python310\lib\asyncio\tasks.py", line 458, in wait_for raise exceptions.TimeoutError() from exc asyncio.exceptions.TimeoutError

Process finished with exit code 1

nieyanzhai avatar Mar 02 '22 14:03 nieyanzhai

Something with your certificates triggers an error. I am also not sure if a public certicate in pem format is supported. Convert the certifcate.pem to a der file via openssl. openssl x509 -outform der -in certificate.pem -out certificate.der But the private key needs to stay in pem!

schroeder- avatar Mar 02 '22 15:03 schroeder-

@schroeder- ".pem" should works as well!

AndreasHeine avatar Mar 02 '22 15:03 AndreasHeine

@NieYanzhai did you add the client cert to the trusted list?

image

AndreasHeine avatar Mar 02 '22 15:03 AndreasHeine

@NieYanzhai did you add the client cert to the trusted list?

image

Yes, client cert added to the trust list.

image

And, I connected to the kepserver using opcua, but can't connect using ayncua.

image

nieyanzhai avatar Mar 03 '22 00:03 nieyanzhai

@schroeder- ".pem" should works as well!

pem works.

nieyanzhai avatar Mar 03 '22 00:03 nieyanzhai

I'm facing the same issue with the exception that gets raised in the log of https://github.com/FreeOpcUa/opcua-asyncio/issues/827#issue-1157287320: TypeError: initializer for ctype 'unsigned char *' must be a cdata pointer, not bytearray

On my side, I was able to work around this by going back to Python version 3.7.9. With version 3.10.1, I'm seeing theTypeError exception.

This looks like a breaking change from: https://github.com/python/cpython/commit/568fb0ff4aa641329261cdde20795b0aa9278175

A brute force patch (not sure if that's the right place to fix it) would be to enforce that _data is bytes and not bytearray in asyncua\common\utils.py:

class Buffer:
    """
    Alternative to io.BytesIO making debug easier
    and added a few convenience methods.
    """

    def __init__(self, data, start_pos=0, size=-1):
        if isinstance(data, bytearray):
            # we need bytes, not bytearray
            self._data = bytes(data)
        else:
            self._data = data

jdess avatar Mar 24 '22 10:03 jdess

After reviewing the code again, I assume that the better place to fix that is in asyncua.client.ua_client.UASocketProtocol.data_received. This is where the data provided from asyncio has changed from bytes to bytearray. In fact, the type hint on this function is not correct any more, either:

    def data_received(self, data: bytes):
        if self.receive_buffer:
            data = self.receive_buffer + data
            self.receive_buffer = None
        self._process_received_data(data)

In this function, if data is a bytearray and not bytes, we probably need to convert it from bytearray to bytes. This would make sure that we re-establish the legacy behavior where data was delivered as bytes. Something along the lines of:

    def data_received(self, data: bytes or bytearray):
        # `data`` might be a `bytearray` in more recent Python versions
        # (see https://github.com/python/cpython/commit/568fb0ff4aa641329261cdde20795b0aa9278175)
        # However, the downstream code still expects to see `bytes`:
        if isinstance(data, bytearray):
            data = bytes(data)

        if self.receive_buffer:
            data = self.receive_buffer + data
            self.receive_buffer = None
        self._process_received_data(data)

If you need a quick patch before this issue gets resolved in an official release, the following snippet works for me:

    # Patch the `data_received` handler to work around the issue
    # https://github.com/python/cpython/commit/568fb0ff4aa641329261cdde20795b0aa9278175
    orig_data_rcvd = asyncua.client.ua_client.UASocketProtocol.data_received

    def data_received_patch(self, data: bytes or bytearray):
        # `data`` might be a `bytearray` in more recent Python versions
        # However, the downstream code still expects to see `bytes`:
        if isinstance(data, bytearray):
            data = bytes(data)

        orig_data_rcvd(self, data)

    asyncua.client.ua_client.UASocketProtocol.data_received = data_received_patch

    # create the OPC-UA client
    client = asyncua.Client(url=server, timeout=timeout)

jdess avatar Mar 24 '22 14:03 jdess