from asyncua import ua from asyncua.crypto.security_policies import ( SecurityPolicyAes128Sha256RsaOaep, SecurityPolicyAes256Sha256RsaPss, SecurityPolicyBasic128Rsa15, SecurityPolicyBasic256, SecurityPolicyBasic256Sha256, ) SESSION_TIMEOUT_MS = 600_000 # 10 min; tune per server (Samarco: long sessions degrade) # connectors.security.policy / .mode (SIENTIAPDE-1974 D4) to the asyncua classes POLICIES = { 'Basic256Sha256': SecurityPolicyBasic256Sha256, 'Aes128Sha256RsaOaep': SecurityPolicyAes128Sha256RsaOaep, 'Aes256Sha256RsaPss': SecurityPolicyAes256Sha256RsaPss, 'Basic256': SecurityPolicyBasic256, 'Basic128Rsa15': SecurityPolicyBasic128Rsa15, } MODES = { 'Sign': ua.MessageSecurityMode.Sign, 'SignAndEncrypt': ua.MessageSecurityMode.SignAndEncrypt, } class UnsupportedSecurityError(ValueError): """The server's security mode or policy has no asyncua counterpart.""" def is_secured(server_cfg: dict) -> bool: """Whether a connection applies security: only with a client certificate (D5), and not when its mode is 'None'. Without one, it connects without security, and a server that requires a certificate refuses the connection.""" security = server_cfg.get('security') or {} return server_cfg.get('certificate') is not None and security.get('mode') != 'None' async def apply_security(client, security, certificate, cert_path, private_key_path): """Apply the server's mode + policy with the connector's client certificate, and set channel > session lifetime (1879 ยง3: secure_channel_timeout = session_timeout * 1.25). The application URI is the certificate's (its SAN URI): the server rejects a different one (KEPServer: BadCertificateUriInvalid). Raises: UnsupportedSecurityError: When the mode or policy is unknown. """ policy = POLICIES.get(security.get('policy')) mode = MODES.get(security.get('mode')) if policy is None or mode is None: raise UnsupportedSecurityError( f'Unsupported OPC security mode={security.get("mode")!r} ' f'policy={security.get("policy")!r} (modes: {", ".join(MODES)}; ' f'policies: {", ".join(POLICIES)})' ) client.application_uri = certificate['application_uri'] await client.set_security( policy, certificate=cert_path, private_key=private_key_path, server_certificate=None, mode=mode, ) client.session_timeout = SESSION_TIMEOUT_MS client.secure_channel_timeout = int(SESSION_TIMEOUT_MS * 1.25)