Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 14 additions & 7 deletions asyncua/client/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@
import logging
import socket
import dataclasses
from cryptography import x509
from cryptography.hazmat.primitives.asymmetric.types import PrivateKeyTypes
from typing import Any, Dict, Iterable, List, Optional, Sequence, Tuple, Type, Union, cast, Callable, Coroutine
from urllib.parse import urlparse, unquote, ParseResult
from pathlib import Path

from cryptography import x509
from cryptography.hazmat.primitives.asymmetric.types import PrivateKeyTypes

import asyncua
from asyncua import ua
from .ua_client import UaClient
Expand Down Expand Up @@ -65,9 +66,9 @@ def __init__(self, url: str, timeout: float = 4, watchdog_intervall: float = 1.0
if have_password:
self._password = unquote(password)

self.name = "Pure Python Async. Client"
self.name = "Pure Python Async Client"
self.description = self.name
self.application_uri = "urn:freeopcua:client"
self.application_uri = "urn:example.org:FreeOpcUa:opcua-asyncio"
self.product_uri = "urn:freeopcua.github.io:client"
self.security_policy = security_policies.SecurityPolicyNone()
self.secure_channel_id = None
Expand Down Expand Up @@ -497,10 +498,16 @@ async def create_session(self) -> ua.CreateSessionResult:
desc.ApplicationName = ua.LocalizedText(self.name)
desc.ApplicationType = ua.ApplicationType.Client
params = ua.CreateSessionParameters()
params.ServerUri = f"urn:{self.server_url.hostname}{self.server_url.path.replace('/', ':')}"
# at least 32 random bytes for server to prove possession of private key (specs part 4, 5.6.2.2)
nonce = create_nonce(32)
params.ClientNonce = nonce
params.ClientCertificate = self.security_policy.host_certificate
if self.security_policy.host_certificate:
params.ClientCertificate = self.security_policy.host_certificate
elif self.user_certificate:
params.ClientCertificate = uacrypto.der_from_x509(self.user_certificate)
else:
params.ClientCertificate = None
params.ClientDescription = desc
params.EndpointUrl = self.server_url.geturl()
params.SessionName = f"{self.description} Session{self._session_counter}"
Expand Down Expand Up @@ -639,7 +646,7 @@ async def activate_session(
"""
Activate session using either username and password or private_key
"""
user_certificate = certificate or self.user_certificate
user_certificate = certificate
params = ua.ActivateSessionParameters()
challenge = b""
if self.security_policy.peer_certificate is not None:
Expand All @@ -652,7 +659,7 @@ async def activate_session(
params.ClientSignature.Algorithm = security_policies.SecurityPolicyBasic256.AsymmetricSignatureURI
params.ClientSignature.Signature = self.security_policy.asymmetric_cryptography.signature(challenge)
params.LocaleIds = self._locale
if not username and not user_certificate:
if not username and not (user_certificate and self.user_private_key):
self._add_anonymous_auth(params)
elif user_certificate:
self._add_certificate_auth(params, user_certificate, challenge)
Expand Down
1 change: 0 additions & 1 deletion asyncua/client/ua_file_transfer.py
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,6 @@ async def create_file(self, file_name: str, request_file_open: bool) -> Tuple[No
and shall be ignored by the caller.
"""
_logger.debug("Request to create file %s in %s", file_name, self._directory_node)
print(f"Request to create file {file_name} in {self._directory_node}")
create_file_node = await self._directory_node.get_child("CreateFile")
arg1_file_name = Variant(file_name, VariantType.String)
arg2_request_file_open = Variant(request_file_open, VariantType.Boolean)
Expand Down
5 changes: 3 additions & 2 deletions asyncua/common/structures104.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ def clean_name(name):
return name
newname = re.sub(r"\W+", "_", name)
newname = re.sub(r"^[0-9]+", r"_\g<0>", newname)
_logger.warning("renamed %s to %s due to Python syntax", name, newname)
_logger.info("renamed %s to %s due to Python syntax", name, newname)
return newname


Expand Down Expand Up @@ -363,7 +363,8 @@ def __add_recursion(sdef, desc):
if parent_sdef:
for sfield in reversed(parent_sdef.Fields):
sdef.Fields.insert(0, sfield)
dtypes.append(DataTypeSorter(desc.NodeId, name, desc, sdef))
if isinstance(sdef, ua.StructureDefinition):
dtypes.append(DataTypeSorter(desc.NodeId, name, desc, sdef))
return _recursive_parse(
server,
server.get_node(desc.NodeId),
Expand Down
21 changes: 20 additions & 1 deletion asyncua/crypto/permission_rules.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
from enum import Enum
from dataclasses import dataclass
from typing import Optional

from asyncua import ua
from asyncua.server.users import UserRole

ADMIN_TYPES = [
ua.ObjectIds.RegisterServerRequest_Encoding_DefaultBinary,
Expand Down Expand Up @@ -37,6 +40,22 @@
]


class UserRole(Enum):
"""
User Roles
"""

Admin = 0
Anonymous = 1
User = 3


@dataclass
class User:
role: UserRole = UserRole.Anonymous
name: Optional[str] = None


class PermissionRuleset:
"""
Base class for permission ruleset
Expand Down
5 changes: 2 additions & 3 deletions asyncua/crypto/truststore.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,9 +139,8 @@ def is_trusted(self, certificate: x509.Certificate) -> bool:
store_ctx.verify_certificate()
_logger.debug("Use trusted certificate : '%s'", _certificate.get_subject().CN)
return True
except crypto.X509StoreContextError as exp:
print(exp)
_logger.warning('Not trusted certificate used: "%s"', _certificate.get_subject().CN)
except crypto.X509StoreContextError:
_logger.exception('Not trusted certificate used: "%s"', _certificate.get_subject().CN)
return False

async def _load_trust_location(self, location: Path):
Expand Down
2 changes: 1 addition & 1 deletion asyncua/server/address_space.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@
] # FIXME Check, if there are missing attribute types.

from asyncua import ua
from asyncua.crypto.permission_rules import User, UserRole

from .users import User, UserRole

_logger = logging.getLogger(__name__)

Expand Down
2 changes: 1 addition & 1 deletion asyncua/server/internal_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
from .address_space import NodeData, AddressSpace, AttributeService, ViewService, NodeManagementService, MethodService
from .subscription_service import SubscriptionService
from .standard_address_space import standard_address_space
from .users import User, UserRole
from asyncua.crypto.permission_rules import User, UserRole
from .internal_session import InternalSession
from .event_generator import EventGenerator
from ..crypto.validator import CertificateValidatorMethod
Expand Down
2 changes: 1 addition & 1 deletion asyncua/server/internal_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from ..common.utils import create_nonce, ServiceError
from ..crypto.uacrypto import x509
from .address_space import AddressSpace
from .users import User, UserRole
from asyncua.crypto.permission_rules import User, UserRole
from .subscription_service import SubscriptionService

if TYPE_CHECKING:
Expand Down
2 changes: 1 addition & 1 deletion asyncua/server/user_managers.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from typing import Union

from asyncua.crypto import uacrypto
from asyncua.server.users import User, UserRole
from asyncua.crypto.permission_rules import User, UserRole


class UserManager:
Expand Down
23 changes: 0 additions & 23 deletions asyncua/server/users.py

This file was deleted.

18 changes: 18 additions & 0 deletions examples/cert-config.cnf
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@

[ req ]
default_bits = 2048
default_md = sha512
distinguished_name = req_distinguished_name
x509_extensions = v3_ext
prompt = no

[req_distinguished_name]
CN= freeopcua@somewhere
O= My Organization
DC= helitack

[ v3_ext ]
subjectAltName = URI:urn:example.org:FreeOpcUa:opcua-asyncio
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
extendedKeyUsage = clientAuth, serverAuth
basicConstraints = critical, CA:false
8 changes: 5 additions & 3 deletions examples/client_to_prosys.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,15 @@ def event_notification(self, event):


async def main():
url = "opc.tcp://localhost:53530/OPCUA/SimulationServer/"
url = "opc.tcp://localhost:53530/OPCUA/SimulationServer"
# url = "opc.tcp://olivier:olivierpass@localhost:53530/OPCUA/SimulationServer/"
async with Client(url=url) as client:
client = Client(url=url)
await client.load_client_certificate("my_cert.der")
async with client:
await client.load_data_type_definitions(overwrite_existing=True)
print("Root children are", await client.nodes.root.get_children())


if __name__ == "__main__":
logging.basicConfig(level=logging.WARN)
logging.basicConfig(level=logging.DEBUG)
asyncio.run(main())
2 changes: 1 addition & 1 deletion examples/client_to_prosys_crypto.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

async def main():
client = Client("opc.tcp://localhost:53530/OPCUA/SimulationServer/")
await client.set_security_string("Basic256Sha256,Sign,certificate-example.der,private-key-example.pem")
await client.set_security_string("Basic256Sha256,Sign,my_cert.der,my_private_key.pem")
client.session_timeout = 2000
async with client:
root = client.nodes.root
Expand Down
10 changes: 9 additions & 1 deletion examples/generate_certificate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,13 @@ Step 3: openssl req -x509 -days 365 -new -out certificate.pem -key key.pem -conf
this way is proved with Siemens OPC UA Client/Server!
'

openssl req -x509 -newkey rsa:4096 -sha256 -keyout my_private_key.pem -out my_cert.pem -days 3650 -nodes -addext "subjectAltName = URI:urn:example.org:FreeOpcUa:python-opcua"



# Step 1: Generate PEM certificate and private key with correct extensions
openssl req -x509 -newkey rsa:4096 -sha512 \
-keyout my_private_key.pem -out my_cert.pem \
-days 3650 -nodes -config cert-config.cnf

# Step 2: Convert certificate to DER format for OPC UA
openssl x509 -outform der -in my_cert.pem -out my_cert.der
Binary file added examples/my_cert.der
Binary file not shown.
33 changes: 33 additions & 0 deletions examples/my_cert.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
-----BEGIN CERTIFICATE-----
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
-----END CERTIFICATE-----
52 changes: 52 additions & 0 deletions examples/my_private_key.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
-----BEGIN PRIVATE KEY-----
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
+uSCWvvuNLawTWfq56DriXUkrXxQn/Y=
-----END PRIVATE KEY-----
2 changes: 1 addition & 1 deletion tests/test_password.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import pytest

from asyncua import Client, Server, ua
from asyncua.server.users import UserRole, User
from asyncua.crypto.permission_rules import UserRole, User

uri = "opc.tcp://127.0.0.1:48517/baz/server"
uri_creds = "opc.tcp://foobar:hR%26yjjGhP%246%40nQ4e@127.0.0.1:48517/baz/server"
Expand Down
2 changes: 1 addition & 1 deletion tests/test_permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from asyncua import Client
from asyncua import Server
from asyncua import ua
from asyncua.server.users import UserRole
from asyncua.crypto.permission_rules import UserRole
from asyncua.server.user_managers import CertificateUserManager
from asyncua.crypto import security_policies

Expand Down