Skip to content

Commit 6b4b3e0

Browse files
cziebuhrChristoph Ziebuhr
authored and
Christoph Ziebuhr
committed
Fix usage of X509IdentityToken
- sign token with algorithm from policy uri - verify signature in server!
1 parent b95867a commit 6b4b3e0

File tree

6 files changed

+158
-47
lines changed

6 files changed

+158
-47
lines changed

asyncua/client/client.py

+7-14
Original file line numberDiff line numberDiff line change
@@ -165,9 +165,9 @@ async def set_security_string(self, string: str) -> None:
165165
Set SecureConnection mode.
166166
:param string: Mode format ``Policy,Mode,certificate,private_key[,server_certificate]``
167167
where:
168-
- ``Policy`` is ``Basic128Rsa15``, ``Basic256`` or ``Basic256Sha256``
168+
- ``Policy`` is ``Basic256Sha256``, ``Aes128Sha256RsaOaep`` or ``Aes256Sha256RsaPss``
169169
- ``Mode`` is ``Sign`` or ``SignAndEncrypt``
170-
- ``certificate`` and ``server_private_key`` are paths to ``.pem`` or ``.der`` files
170+
- ``certificate`` and ``server_certificate`` are paths to ``.pem`` or ``.der`` files
171171
- ``private_key`` may be a path to a ``.pem`` or ``.der`` file or a conjunction of ``path``::``password`` where
172172
``password`` is the private key password.
173173
Call this before connect()
@@ -669,18 +669,11 @@ def _add_certificate_auth(self, params, certificate, challenge):
669669
params.UserIdentityToken.CertificateData = uacrypto.der_from_x509(certificate)
670670
# specs part 4, 5.6.3.1: the data to sign is created by appending
671671
# the last serverNonce to the serverCertificate
672-
params.UserTokenSignature = ua.SignatureData()
673-
# use signature algorithm that was used for certificate generation
674-
if certificate.signature_hash_algorithm.name == "sha256":
675-
params.UserIdentityToken.PolicyId = self.server_policy(ua.UserTokenType.Certificate).PolicyId
676-
sig = uacrypto.sign_sha256(self.user_private_key, challenge)
677-
params.UserTokenSignature.Algorithm = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"
678-
params.UserTokenSignature.Signature = sig
679-
else:
680-
params.UserIdentityToken.PolicyId = self.server_policy(ua.UserTokenType.Certificate).PolicyId
681-
sig = uacrypto.sign_sha1(self.user_private_key, challenge)
682-
params.UserTokenSignature.Algorithm = "http://www.w3.org/2000/09/xmldsig#rsa-sha1"
683-
params.UserTokenSignature.Signature = sig
672+
policy = self.server_policy(ua.UserTokenType.Certificate)
673+
sig, alg = security_policies.sign_asymmetric(self.user_private_key, challenge, policy.SecurityPolicyUri)
674+
params.UserIdentityToken.PolicyId = policy.PolicyId
675+
params.UserTokenSignature.Algorithm = alg
676+
params.UserTokenSignature.Signature = sig
684677

685678
def _add_user_auth(self, params, username: str, password: str):
686679
params.UserIdentityToken = ua.UserNameIdentityToken()

asyncua/crypto/security_policies.py

+40
Original file line numberDiff line numberDiff line change
@@ -564,6 +564,10 @@ class SecurityPolicyAes128Sha256RsaOaep(SecurityPolicy):
564564
def encrypt_asymmetric(pubkey, data):
565565
return uacrypto.encrypt_rsa_oaep(pubkey, data)
566566

567+
@staticmethod
568+
def sign_asymmetric(privkey, data):
569+
return uacrypto.sign_sha256(privkey, data)
570+
567571
def __init__(self, peer_cert, host_cert, host_privkey, mode, permission_ruleset=None):
568572
if isinstance(peer_cert, bytes):
569573
peer_cert = uacrypto.x509_from_der(peer_cert)
@@ -638,6 +642,10 @@ class SecurityPolicyAes256Sha256RsaPss(SecurityPolicy):
638642
def encrypt_asymmetric(pubkey, data):
639643
return uacrypto.encrypt_rsa_oaep_sha256(pubkey, data)
640644

645+
@staticmethod
646+
def sign_asymmetric(privkey, data):
647+
return uacrypto.sign_pss_sha256(privkey, data)
648+
641649
def __init__(self, peer_cert, host_cert, host_privkey, mode, permission_ruleset=None):
642650
if isinstance(peer_cert, bytes):
643651
peer_cert = uacrypto.x509_from_der(peer_cert)
@@ -718,6 +726,10 @@ class SecurityPolicyBasic128Rsa15(SecurityPolicy):
718726
def encrypt_asymmetric(pubkey, data):
719727
return uacrypto.encrypt_rsa15(pubkey, data)
720728

729+
@staticmethod
730+
def sign_asymmetric(privkey, data):
731+
return uacrypto.sign_sha1(privkey, data)
732+
721733
def __init__(self, peer_cert, host_cert, host_privkey, mode, permission_ruleset=None):
722734
_logger.warning("DEPRECATED! Do not use SecurityPolicyBasic128Rsa15 anymore!")
723735

@@ -798,6 +810,10 @@ class SecurityPolicyBasic256(SecurityPolicy):
798810
def encrypt_asymmetric(pubkey, data):
799811
return uacrypto.encrypt_rsa_oaep(pubkey, data)
800812

813+
@staticmethod
814+
def sign_asymmetric(privkey, data):
815+
return uacrypto.sign_sha1(privkey, data)
816+
801817
def __init__(self, peer_cert, host_cert, host_privkey, mode, permission_ruleset=None):
802818
_logger.warning("DEPRECATED! Do not use SecurityPolicyBasic256 anymore!")
803819

@@ -878,6 +894,10 @@ class SecurityPolicyBasic256Sha256(SecurityPolicy):
878894
def encrypt_asymmetric(pubkey, data):
879895
return uacrypto.encrypt_rsa_oaep(pubkey, data)
880896

897+
@staticmethod
898+
def sign_asymmetric(privkey, data):
899+
return uacrypto.sign_sha256(privkey, data)
900+
881901
def __init__(self, peer_cert, host_cert, host_privkey, mode, permission_ruleset=None):
882902
if isinstance(peer_cert, bytes):
883903
peer_cert = uacrypto.x509_from_der(peer_cert)
@@ -961,6 +981,26 @@ def create(self, peer_certificate):
961981
)
962982

963983

984+
def sign_asymmetric(privkey, data, policy_uri):
985+
"""
986+
Sign data with privkey using an asymmetric algorithm.
987+
The algorithm is selected by policy_uri.
988+
Returns a tuple (signature, algorithm_uri)
989+
"""
990+
for cls in [
991+
SecurityPolicyBasic256Sha256,
992+
SecurityPolicyBasic256,
993+
SecurityPolicyBasic128Rsa15,
994+
SecurityPolicyAes128Sha256RsaOaep,
995+
SecurityPolicyAes256Sha256RsaPss,
996+
]:
997+
if policy_uri == cls.URI:
998+
return (cls.sign_asymmetric(privkey, data), cls.AsymmetricSignatureURI)
999+
if not policy_uri or policy_uri == SecurityPolicyNone.URI:
1000+
return data, ""
1001+
raise UaError(f"Unsupported security policy `{policy_uri}`")
1002+
1003+
9641004
# policy, mode, security_level
9651005
SECURITY_POLICY_TYPE_MAP = {
9661006
SecurityPolicyType.NoSecurity: [SecurityPolicyNone, MessageSecurityMode.None_, 0],

asyncua/server/internal_server.py

+31
Original file line numberDiff line numberDiff line change
@@ -409,3 +409,34 @@ def decrypt_user_token(self, isession, token):
409409
password = password.decode("utf-8")
410410

411411
return user_name, password
412+
413+
def verify_x509_token(self, isession, token, signature):
414+
"""
415+
verify certificate signature
416+
"""
417+
cert = uacrypto.x509_from_der(token.CertificateData)
418+
alg = signature.Algorithm
419+
sig = signature.Signature
420+
421+
# TODO check if algorithm is allowed, throw BadSecurityPolicyRejected if not
422+
423+
challenge = b""
424+
if self.certificate is not None:
425+
challenge += uacrypto.der_from_x509(self.certificate)
426+
if isession.nonce is not None:
427+
challenge += isession.nonce
428+
429+
if not (alg and sig):
430+
raise ValueError("No signature")
431+
432+
if alg == "http://www.w3.org/2000/09/xmldsig#rsa-sha1":
433+
uacrypto.verify_sha1(cert, challenge, sig)
434+
elif alg == "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256":
435+
uacrypto.verify_sha256(cert, challenge, sig)
436+
elif alg == "http://opcfoundation.org/UA/security/rsa-pss-sha2-256":
437+
uacrypto.verify_pss_sha256(cert, challenge, sig)
438+
else:
439+
self.logger.warning("Unknown certificate signature algorithm %s", alg)
440+
raise ValueError("Unknown algorithm")
441+
442+
return token.CertificateData

asyncua/server/internal_session.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -126,8 +126,7 @@ def activate_session(self, params, peer_certificate):
126126
if isinstance(id_token, ua.UserNameIdentityToken):
127127
username, password = self.iserver.decrypt_user_token(self, id_token)
128128
elif isinstance(id_token, ua.X509IdentityToken):
129-
# TODO implement verify_x509_token
130-
peer_certificate = id_token.CertificateData
129+
peer_certificate = self.iserver.verify_x509_token(self, id_token, params.UserTokenSignature)
131130
username, password = None, None
132131
else:
133132
username, password = None, None

asyncua/server/server.py

+23-13
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
import asyncio
66
import logging
77
import math
8-
from cryptography import x509
98
from datetime import timedelta, datetime
109
import socket
1110
from urllib.parse import urlparse
@@ -113,7 +112,6 @@ def __init__(self, iserver: InternalServer = None, user_manager=None):
113112
]
114113
# allow all certificates by default
115114
self._permission_ruleset = SimpleRoleRuleset()
116-
self.certificate: Optional[x509.Certificate] = None
117115
# Use acceptable limits
118116
buffer_sz = 65535
119117
max_msg_sz = 100 * 1024 * 1024 # 100mb
@@ -233,7 +231,7 @@ async def load_certificate(self, path_or_content: Union[str, bytes, Path], forma
233231
"""
234232
load server certificate from file, either pem or der
235233
"""
236-
self.certificate = await uacrypto.load_certificate(path_or_content, format)
234+
self.iserver.certificate = await uacrypto.load_certificate(path_or_content, format)
237235

238236
async def load_private_key(self, path_or_content: Union[str, Path, bytes], password=None, format=None):
239237
self.iserver.private_key = await uacrypto.load_private_key(path_or_content, password, format)
@@ -385,7 +383,7 @@ async def _setup_server_nodes(self):
385383
for policy_type in self._security_policy:
386384
policy, mode, level = security_policies.SECURITY_POLICY_TYPE_MAP[policy_type]
387385
if policy is not security_policies.SecurityPolicyNone and not (
388-
self.certificate and self.iserver.private_key
386+
self.iserver.certificate and self.iserver.private_key
389387
):
390388
no_cert = True
391389
continue
@@ -394,7 +392,7 @@ async def _setup_server_nodes(self):
394392
security_policies.SecurityPolicyFactory(
395393
policy,
396394
mode,
397-
self.certificate,
395+
self.iserver.certificate,
398396
self.iserver.private_key,
399397
permission_ruleset=self._permission_ruleset,
400398
)
@@ -417,9 +415,21 @@ def _set_endpoints(self, policy, mode, level):
417415
idtoken = ua.UserTokenPolicy()
418416
idtoken.PolicyId = "certificate"
419417
idtoken.TokenType = ua.UserTokenType.Certificate
420-
idtoken.SecurityPolicyUri = policy.URI
421-
# TODO request signing if mode == ua.MessageSecurityMode.None_ (also need to verify signature then)
422-
idtokens.append(idtoken)
418+
# always request signing
419+
if mode == ua.MessageSecurityMode.None_:
420+
# find first policy with signing
421+
for token_policy_type in self._security_policy:
422+
token_policy, token_mode, _ = security_policies.SECURITY_POLICY_TYPE_MAP[token_policy_type]
423+
if token_mode == ua.MessageSecurityMode.None_:
424+
continue
425+
idtoken.SecurityPolicyUri = token_policy.URI
426+
idtokens.append(idtoken)
427+
break
428+
else:
429+
_logger.warning("No signing policy available, user certificate cannot get verified")
430+
else:
431+
idtoken.SecurityPolicyUri = policy.URI
432+
idtokens.append(idtoken)
423433

424434
if ua.UserNameIdentityToken in tokens:
425435
idtoken = ua.UserTokenPolicy()
@@ -432,7 +442,7 @@ def _set_endpoints(self, policy, mode, level):
432442
# use same policy for encryption
433443
idtoken.SecurityPolicyUri = policy.URI
434444
# try to avoid plaintext password, find first policy with encryption
435-
elif self.certificate and self.iserver.private_key:
445+
elif self.iserver.certificate and self.iserver.private_key:
436446
for token_policy_type in self._security_policy:
437447
token_policy, token_mode, _ = security_policies.SECURITY_POLICY_TYPE_MAP[token_policy_type]
438448
if token_mode != ua.MessageSecurityMode.SignAndEncrypt:
@@ -457,8 +467,8 @@ def _set_endpoints(self, policy, mode, level):
457467
edp = ua.EndpointDescription()
458468
edp.EndpointUrl = self.endpoint.geturl()
459469
edp.Server = appdesc
460-
if self.certificate:
461-
edp.ServerCertificate = uacrypto.der_from_x509(self.certificate)
470+
if self.iserver.certificate:
471+
edp.ServerCertificate = uacrypto.der_from_x509(self.iserver.certificate)
462472
edp.SecurityMode = mode
463473
edp.SecurityPolicyUri = policy.URI
464474
edp.UserIdentityTokens = idtokens
@@ -473,9 +483,9 @@ async def start(self):
473483
"""
474484
Start to listen on network
475485
"""
476-
if self.certificate is not None:
486+
if self.iserver.certificate is not None:
477487
# Log warnings about the certificate
478-
uacrypto.check_certificate(self.certificate, self._application_uri, socket.gethostname())
488+
uacrypto.check_certificate(self.iserver.certificate, self._application_uri, socket.gethostname())
479489
await self._setup_server_nodes()
480490
await self.iserver.start()
481491
try:

0 commit comments

Comments
 (0)