mirror of
				https://github.com/FreeOpcUa/opcua-asyncio
				synced 2025-10-29 17:07:18 +08:00 
			
		
		
		
	support communication x509 certificate chain
This commit is contained in:
		 Hryhorii Biloshenko
					Hryhorii Biloshenko
				
			
				
					committed by
					
						 oroulet
						oroulet
					
				
			
			
				
	
			
			
			 oroulet
						oroulet
					
				
			
						parent
						
							2b6f3e5bd6
						
					
				
				
					commit
					a43af60bd1
				
			| @@ -1,29 +1,30 @@ | ||||
| import asyncio | ||||
| import dataclasses | ||||
| import logging | ||||
| import socket | ||||
| import dataclasses | ||||
| 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 typing import Any, Callable, Coroutine, Dict, Iterable, List, Optional, Sequence, Tuple, Type, Union, cast | ||||
| from urllib.parse import ParseResult, unquote, urlparse | ||||
|  | ||||
| from cryptography import x509 | ||||
| from cryptography.hazmat.primitives.asymmetric.types import PrivateKeyTypes | ||||
|  | ||||
| import asyncua | ||||
| from asyncua import ua | ||||
| from .ua_client import UaClient | ||||
| from ..common.xmlimporter import XmlImporter | ||||
| from ..common.xmlexporter import XmlExporter | ||||
| from ..common.node import Node | ||||
|  | ||||
| from ..common.manage_nodes import delete_nodes | ||||
| from ..common.subscription import Subscription, SubscriptionHandler | ||||
| from ..common.node import Node | ||||
| from ..common.shortcuts import Shortcuts | ||||
| from ..common.structures import load_type_definitions, load_enums | ||||
| from ..common.structures import load_enums, load_type_definitions | ||||
| from ..common.structures104 import load_data_type_definitions | ||||
| from ..common.utils import create_nonce, ServiceError | ||||
| from ..common.ua_utils import value_to_datavalue, copy_dataclass_attr | ||||
| from ..crypto import uacrypto, security_policies | ||||
| from ..common.subscription import Subscription, SubscriptionHandler | ||||
| from ..common.ua_utils import copy_dataclass_attr, value_to_datavalue | ||||
| from ..common.utils import ServiceError, create_nonce | ||||
| from ..common.xmlexporter import XmlExporter | ||||
| from ..common.xmlimporter import XmlImporter | ||||
| from ..crypto import security_policies, uacrypto | ||||
| from ..crypto.validator import CertificateValidatorMethod | ||||
| from .ua_client import UaClient | ||||
|  | ||||
| _logger = logging.getLogger(__name__) | ||||
|  | ||||
| @@ -198,6 +199,7 @@ class Client: | ||||
|         private_key_password: Optional[Union[str, bytes]] = None, | ||||
|         server_certificate: Optional[Union[str, uacrypto.CertProperties, bytes]] = None, | ||||
|         mode: ua.MessageSecurityMode = ua.MessageSecurityMode.SignAndEncrypt, | ||||
|         certificate_chain: Optional[Sequence[Union[str, uacrypto.CertProperties, bytes, Path]]] = None, | ||||
|     ) -> None: | ||||
|         """ | ||||
|         Set SecureConnection mode. | ||||
| @@ -222,9 +224,14 @@ class Client: | ||||
|             server_certificate = uacrypto.CertProperties(server_certificate) | ||||
|         if not isinstance(certificate, uacrypto.CertProperties): | ||||
|             certificate = uacrypto.CertProperties(certificate) | ||||
|         certificate_chain = certificate_chain or [] | ||||
|         chain = [ | ||||
|             cert if isinstance(cert, uacrypto.CertProperties) else uacrypto.CertProperties(cert) | ||||
|             for cert in certificate_chain | ||||
|         ] | ||||
|         if not isinstance(private_key, uacrypto.CertProperties): | ||||
|             private_key = uacrypto.CertProperties(private_key, password=private_key_password) | ||||
|         return await self._set_security(policy, certificate, private_key, server_certificate, mode) | ||||
|         return await self._set_security(policy, certificate, private_key, server_certificate, mode, chain) | ||||
|  | ||||
|     async def _set_security( | ||||
|         self, | ||||
| @@ -233,17 +240,22 @@ class Client: | ||||
|         private_key: uacrypto.CertProperties, | ||||
|         server_cert: uacrypto.CertProperties, | ||||
|         mode: ua.MessageSecurityMode = ua.MessageSecurityMode.SignAndEncrypt, | ||||
|         certificate_chain: Optional[Sequence[uacrypto.CertProperties]] = None, | ||||
|     ) -> None: | ||||
|         if isinstance(server_cert, uacrypto.CertProperties): | ||||
|             server_cert = await uacrypto.load_certificate(server_cert.path_or_content, server_cert.extension) | ||||
|         cert = await uacrypto.load_certificate(certificate.path_or_content, certificate.extension) | ||||
|         certificate_chain = certificate_chain or [] | ||||
|         chain = await asyncio.gather( | ||||
|             *(uacrypto.load_certificate(cert.path_or_content, cert.extension) for cert in certificate_chain) | ||||
|         ) | ||||
|         pk = await uacrypto.load_private_key( | ||||
|             private_key.path_or_content, | ||||
|             private_key.password, | ||||
|             private_key.extension, | ||||
|         ) | ||||
|         uacrypto.check_certificate(cert, self.application_uri, socket.gethostname()) | ||||
|         self.security_policy = policy(server_cert, cert, pk, mode)  # type: ignore | ||||
|         self.security_policy = policy(server_cert, cert, pk, mode, host_cert_chain=chain) | ||||
|         self.uaclient.set_security(self.security_policy) | ||||
|  | ||||
|     async def load_client_certificate(self, path: str, extension: Optional[str] = None) -> None: | ||||
|   | ||||
| @@ -1,14 +1,14 @@ | ||||
| from dataclasses import dataclass | ||||
| import hashlib | ||||
| from datetime import datetime, timedelta, timezone | ||||
| import logging | ||||
| import copy | ||||
| import hashlib | ||||
| import logging | ||||
| from dataclasses import dataclass | ||||
| from datetime import datetime, timedelta, timezone | ||||
|  | ||||
| from asyncua import ua | ||||
| from asyncua.ua.uaerrors import UaInvalidParameterError | ||||
| from ..ua.ua_binary import struct_from_binary, struct_to_binary, header_from_binary, header_to_binary | ||||
| from ..crypto.uacrypto import InvalidSignature | ||||
|  | ||||
| from ..crypto.uacrypto import InvalidSignature | ||||
| from ..ua.ua_binary import header_from_binary, header_to_binary, struct_from_binary, struct_to_binary | ||||
|  | ||||
| _logger = logging.getLogger("asyncua.uaprotocol") | ||||
|  | ||||
| @@ -190,6 +190,8 @@ class MessageChunk: | ||||
|             chunk.SecurityHeader.SecurityPolicyURI = security_policy.URI | ||||
|             if security_policy.host_certificate and security_policy.Mode != ua.MessageSecurityMode.None_: | ||||
|                 chunk.SecurityHeader.SenderCertificate = security_policy.host_certificate | ||||
|                 for cert in security_policy.host_certificate_chain: | ||||
|                     chunk.SecurityHeader.SenderCertificate += cert | ||||
|             if security_policy.peer_certificate: | ||||
|                 chunk.SecurityHeader.ReceiverCertificateThumbPrint = hashlib.sha1( | ||||
|                     security_policy.peer_certificate | ||||
|   | ||||
| @@ -1,18 +1,18 @@ | ||||
| from __future__ import annotations | ||||
|  | ||||
| import logging | ||||
| import struct | ||||
| import time | ||||
| from abc import ABCMeta, abstractmethod | ||||
| from typing import TYPE_CHECKING, Optional | ||||
|  | ||||
| from abc import ABCMeta, abstractmethod | ||||
| from ..ua import SecurityPolicyType, MessageSecurityMode, UaError | ||||
| from ..ua import MessageSecurityMode, SecurityPolicyType, UaError | ||||
|  | ||||
| if TYPE_CHECKING: | ||||
|     from ..crypto.permission_rules import PermissionRuleset | ||||
|  | ||||
| from ..crypto import uacrypto | ||||
|  | ||||
|  | ||||
| _logger = logging.getLogger(__name__) | ||||
|  | ||||
|  | ||||
| @@ -488,9 +488,10 @@ class SecurityPolicy: | ||||
|     peer_certificate: Optional[bytes] | ||||
|     host_certificate: Optional[bytes] | ||||
|     permissions: Optional[PermissionRuleset] | ||||
|     host_certificate_chain: list[bytes] | ||||
|  | ||||
|     @abstractmethod | ||||
|     def __init__(self, peer_cert, host_cert, host_privkey, mode, permission_ruleset=None): | ||||
|     def __init__(self, peer_cert, host_cert, host_privkey, mode, permission_ruleset=None, host_cert_chain=None): | ||||
|         pass | ||||
|  | ||||
|     @abstractmethod | ||||
| @@ -509,7 +510,13 @@ class SecurityPolicyNone(SecurityPolicy): | ||||
|     secure_channel_nonce_length: int = 0 | ||||
|  | ||||
|     def __init__( | ||||
|         self, peer_cert=None, host_cert=None, host_privkey=None, mode=MessageSecurityMode.None_, permission_ruleset=None | ||||
|         self, | ||||
|         peer_cert=None, | ||||
|         host_cert=None, | ||||
|         host_privkey=None, | ||||
|         mode=MessageSecurityMode.None_, | ||||
|         permission_ruleset=None, | ||||
|         host_cert_chain=None, | ||||
|     ): | ||||
|         if isinstance(peer_cert, bytes): | ||||
|             peer_cert = uacrypto.x509_from_der(peer_cert) | ||||
| @@ -518,6 +525,8 @@ class SecurityPolicyNone(SecurityPolicy): | ||||
|         self.Mode = mode | ||||
|         self.peer_certificate = uacrypto.der_from_x509(peer_cert) | ||||
|         self.host_certificate = uacrypto.der_from_x509(host_cert) | ||||
|         host_cert_chain = host_cert_chain or [] | ||||
|         self.host_certificate_chain = [uacrypto.der_from_x509(cert) for cert in host_cert_chain] | ||||
|         self.permissions = permission_ruleset | ||||
|  | ||||
|     def make_local_symmetric_key(self, secret, seed): | ||||
| @@ -568,7 +577,7 @@ class SecurityPolicyAes128Sha256RsaOaep(SecurityPolicy): | ||||
|     def sign_asymmetric(privkey, data): | ||||
|         return uacrypto.sign_sha256(privkey, data) | ||||
|  | ||||
|     def __init__(self, peer_cert, host_cert, host_privkey, mode, permission_ruleset=None): | ||||
|     def __init__(self, peer_cert, host_cert, host_privkey, mode, permission_ruleset=None, host_cert_chain=None): | ||||
|         if isinstance(peer_cert, bytes): | ||||
|             peer_cert = uacrypto.x509_from_der(peer_cert) | ||||
|         # even in Sign mode we need to asymmetrically encrypt secrets | ||||
| @@ -582,6 +591,8 @@ class SecurityPolicyAes128Sha256RsaOaep(SecurityPolicy): | ||||
|         self.Mode = mode | ||||
|         self.peer_certificate = uacrypto.der_from_x509(peer_cert) | ||||
|         self.host_certificate = uacrypto.der_from_x509(host_cert) | ||||
|         host_cert_chain = host_cert_chain or [] | ||||
|         self.host_certificate_chain = [uacrypto.der_from_x509(cert) for cert in host_cert_chain] | ||||
|         self.permissions = permission_ruleset | ||||
|  | ||||
|     def make_local_symmetric_key(self, secret, seed): | ||||
| @@ -646,7 +657,7 @@ class SecurityPolicyAes256Sha256RsaPss(SecurityPolicy): | ||||
|     def sign_asymmetric(privkey, data): | ||||
|         return uacrypto.sign_pss_sha256(privkey, data) | ||||
|  | ||||
|     def __init__(self, peer_cert, host_cert, host_privkey, mode, permission_ruleset=None): | ||||
|     def __init__(self, peer_cert, host_cert, host_privkey, mode, permission_ruleset=None, host_cert_chain=None): | ||||
|         if isinstance(peer_cert, bytes): | ||||
|             peer_cert = uacrypto.x509_from_der(peer_cert) | ||||
|         # even in Sign mode we need to asymmetrically encrypt secrets | ||||
| @@ -660,6 +671,8 @@ class SecurityPolicyAes256Sha256RsaPss(SecurityPolicy): | ||||
|         self.Mode = mode | ||||
|         self.peer_certificate = uacrypto.der_from_x509(peer_cert) | ||||
|         self.host_certificate = uacrypto.der_from_x509(host_cert) | ||||
|         host_cert_chain = host_cert_chain or [] | ||||
|         self.host_certificate_chain = [uacrypto.der_from_x509(cert) for cert in host_cert_chain] | ||||
|         self.permissions = permission_ruleset | ||||
|  | ||||
|     def make_local_symmetric_key(self, secret, seed): | ||||
| @@ -730,7 +743,7 @@ class SecurityPolicyBasic128Rsa15(SecurityPolicy): | ||||
|     def sign_asymmetric(privkey, data): | ||||
|         return uacrypto.sign_sha1(privkey, data) | ||||
|  | ||||
|     def __init__(self, peer_cert, host_cert, host_privkey, mode, permission_ruleset=None): | ||||
|     def __init__(self, peer_cert, host_cert, host_privkey, mode, permission_ruleset=None, host_cert_chain=None): | ||||
|         _logger.warning("DEPRECATED! Do not use SecurityPolicyBasic128Rsa15 anymore!") | ||||
|  | ||||
|         if isinstance(peer_cert, bytes): | ||||
| @@ -746,6 +759,8 @@ class SecurityPolicyBasic128Rsa15(SecurityPolicy): | ||||
|         self.Mode = mode | ||||
|         self.peer_certificate = uacrypto.der_from_x509(peer_cert) | ||||
|         self.host_certificate = uacrypto.der_from_x509(host_cert) | ||||
|         host_cert_chain = host_cert_chain or [] | ||||
|         self.host_certificate_chain = [uacrypto.der_from_x509(cert) for cert in host_cert_chain] | ||||
|         self.permissions = permission_ruleset | ||||
|  | ||||
|     def make_local_symmetric_key(self, secret, seed): | ||||
| @@ -814,7 +829,7 @@ class SecurityPolicyBasic256(SecurityPolicy): | ||||
|     def sign_asymmetric(privkey, data): | ||||
|         return uacrypto.sign_sha1(privkey, data) | ||||
|  | ||||
|     def __init__(self, peer_cert, host_cert, host_privkey, mode, permission_ruleset=None): | ||||
|     def __init__(self, peer_cert, host_cert, host_privkey, mode, permission_ruleset=None, host_cert_chain=None): | ||||
|         _logger.warning("DEPRECATED! Do not use SecurityPolicyBasic256 anymore!") | ||||
|  | ||||
|         if isinstance(peer_cert, bytes): | ||||
| @@ -830,6 +845,8 @@ class SecurityPolicyBasic256(SecurityPolicy): | ||||
|         self.Mode = mode | ||||
|         self.peer_certificate = uacrypto.der_from_x509(peer_cert) | ||||
|         self.host_certificate = uacrypto.der_from_x509(host_cert) | ||||
|         host_cert_chain = host_cert_chain or [] | ||||
|         self.host_certificate_chain = [uacrypto.der_from_x509(cert) for cert in host_cert_chain] | ||||
|         self.permissions = permission_ruleset | ||||
|  | ||||
|     def make_local_symmetric_key(self, secret, seed): | ||||
| @@ -898,7 +915,7 @@ class SecurityPolicyBasic256Sha256(SecurityPolicy): | ||||
|     def sign_asymmetric(privkey, data): | ||||
|         return uacrypto.sign_sha256(privkey, data) | ||||
|  | ||||
|     def __init__(self, peer_cert, host_cert, host_privkey, mode, permission_ruleset=None): | ||||
|     def __init__(self, peer_cert, host_cert, host_privkey, mode, permission_ruleset=None, host_cert_chain=None): | ||||
|         if isinstance(peer_cert, bytes): | ||||
|             peer_cert = uacrypto.x509_from_der(peer_cert) | ||||
|         # even in Sign mode we need to asymmetrically encrypt secrets | ||||
| @@ -912,6 +929,8 @@ class SecurityPolicyBasic256Sha256(SecurityPolicy): | ||||
|         self.Mode = mode | ||||
|         self.peer_certificate = uacrypto.der_from_x509(peer_cert) | ||||
|         self.host_certificate = uacrypto.der_from_x509(host_cert) | ||||
|         host_cert_chain = host_cert_chain or [] | ||||
|         self.host_certificate_chain = [uacrypto.der_from_x509(cert) for cert in host_cert_chain] | ||||
|         self.permissions = permission_ruleset | ||||
|  | ||||
|     def make_local_symmetric_key(self, secret, seed): | ||||
| @@ -965,11 +984,12 @@ class SecurityPolicyFactory: | ||||
|     SecurityPolicy for every client and client's certificate | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, cls, mode, certificate=None, private_key=None, permission_ruleset=None): | ||||
|     def __init__(self, cls, mode, certificate=None, private_key=None, permission_ruleset=None, certificate_chain=None): | ||||
|         self.cls = cls | ||||
|         self.mode = mode | ||||
|         self.certificate = certificate | ||||
|         self.private_key = private_key | ||||
|         self.certificate_chain = certificate_chain | ||||
|         self.permission_ruleset = permission_ruleset | ||||
|  | ||||
|     def matches(self, uri, mode=None): | ||||
| @@ -977,7 +997,12 @@ class SecurityPolicyFactory: | ||||
|  | ||||
|     def create(self, peer_certificate): | ||||
|         return self.cls( | ||||
|             peer_certificate, self.certificate, self.private_key, self.mode, permission_ruleset=self.permission_ruleset | ||||
|             peer_certificate, | ||||
|             self.certificate, | ||||
|             self.private_key, | ||||
|             self.mode, | ||||
|             permission_ruleset=self.permission_ruleset, | ||||
|             host_cert_chain=self.certificate_chain, | ||||
|         ) | ||||
|  | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user