Commit be43b413 authored by ORD's avatar ORD

Merge pull request #81 from alkor/security

Security policies implementation
parents a80a7be6 a2d79771
...@@ -10,15 +10,6 @@ from concurrent.futures import Future ...@@ -10,15 +10,6 @@ from concurrent.futures import Future
import opcua.uaprotocol as ua import opcua.uaprotocol as ua
import opcua.utils as utils import opcua.utils as utils
class CachedRequest(object):
def __init__(self, binary):
self.binary = binary
def to_binary(self):
return self.binary
class UASocketClient(object): class UASocketClient(object):
""" """
handle socket connection and send ua messages handle socket connection and send ua messages
...@@ -111,62 +102,21 @@ class UASocketClient(object): ...@@ -111,62 +102,21 @@ class UASocketClient(object):
break break
self.logger.info("Thread ended") self.logger.info("Thread ended")
def _receive_header(self):
self.logger.debug("Waiting for header")
header = ua.Header.from_string(self._socket)
self.logger.info("received header: %s", header)
return header
def _receive_body(self, size):
self.logger.debug("reading body of message (%s bytes)", size)
data = self._socket.read(size)
if size != len(data):
raise Exception("Error, did not receive expected number of bytes, got {}, asked for {}".format(len(data), size))
return utils.Buffer(data)
def _receive(self): def _receive(self):
body_chunk = b"" msg = ua.tcp_message_from_socket(self._security_policy, self._socket)
while True: if isinstance(msg, ua.MessageChunk):
ret = self._receive_complete_msg() chunks = [msg]
if ret is None: # TODO: check everything
return while chunks[-1].MessageHeader.ChunkType == ua.ChunkType.Intermediate:
hdr, algohdr, seqhdr, body = ret chunks.append(ua.tcp_message_from_socket(self._security_policy, self._socket))
if hdr.ChunkType in (b"F", b"A"): body = b"".join([c.Body for c in chunks])
body.data = body_chunk + body.data self._call_callback(msg.SequenceHeader.RequestId, utils.Buffer(body))
break elif isinstance(msg, ua.Acknowledge):
elif hdr.ChunkType == b"C": self._call_callback(0, msg)
self.logger.debug("Received an intermediate message with header %s, waiting for next message", hdr) elif isinstance(msg, ua.ErrorMessage):
body_chunk += body.data self.logger.warning("Received an error: {}".format(msg))
else:
self.logger.warning("Received a message with unknown ChunkType %s, in header %s", hdr.ChunkType, hdr)
return
self._call_callback(seqhdr.RequestId, body)
def _receive_complete_msg(self):
header = self._receive_header()
if header is None:
return None
body = self._receive_body(header.body_size)
if header.MessageType == ua.MessageType.Error:
self.logger.warning("Received an error message type")
err = ua.ErrorMessage.from_binary(body)
self.logger.warning(err)
return None
if header.MessageType == ua.MessageType.Acknowledge:
self._call_callback(0, body)
return None
elif header.MessageType == ua.MessageType.SecureOpen:
algohdr = ua.AsymmetricAlgorithmHeader.from_binary(body)
self.logger.info(algohdr)
elif header.MessageType == ua.MessageType.SecureMessage:
algohdr = ua.SymmetricAlgorithmHeader.from_binary(body)
self.logger.info(algohdr)
else: else:
self.logger.warning("Unsupported message type: %s", header.MessageType) raise Exception("Unsupported message type: {}".format(msg))
return None
seqhdr = ua.SequenceHeader.from_binary(body)
self.logger.debug(seqhdr)
return header, algohdr, seqhdr, body
def _call_callback(self, request_id, body): def _call_callback(self, request_id, body):
with self._lock: with self._lock:
...@@ -219,7 +169,7 @@ class UASocketClient(object): ...@@ -219,7 +169,7 @@ class UASocketClient(object):
with self._lock: with self._lock:
self._callbackmap[0] = future self._callbackmap[0] = future
self._write_socket(header, hello) self._write_socket(header, hello)
ack = ua.Acknowledge.from_binary(future.result(self.timeout)) ack = future.result(self.timeout)
self._max_chunk_size = ack.SendBufferSize # client shouldn't send chunks larger than this self._max_chunk_size = ack.SendBufferSize # client shouldn't send chunks larger than this
return ack return ack
...@@ -262,18 +212,19 @@ class BinaryClient(object): ...@@ -262,18 +212,19 @@ class BinaryClient(object):
uaprotocol_auto.py and uaprotocol_hand.py uaprotocol_auto.py and uaprotocol_hand.py
""" """
def __init__(self, timeout=1): def __init__(self, timeout=1, security_policy=ua.SecurityPolicy()):
self.logger = logging.getLogger(__name__) self.logger = logging.getLogger(__name__)
self._publishcallbacks = {} self._publishcallbacks = {}
self._lock = Lock() self._lock = Lock()
self._timeout = timeout self._timeout = timeout
self._uasocket = None self._uasocket = None
self._security_policy = security_policy
def connect_socket(self, host, port): def connect_socket(self, host, port):
""" """
connect to server socket and start receiving thread connect to server socket and start receiving thread
""" """
self._uasocket = UASocketClient(self._timeout) self._uasocket = UASocketClient(self._timeout, security_policy=self._security_policy)
return self._uasocket.connect_socket(host, port) return self._uasocket.connect_socket(host, port)
def disconnect_socket(self): def disconnect_socket(self):
......
...@@ -67,7 +67,7 @@ class Client(object): ...@@ -67,7 +67,7 @@ class Client(object):
which offers a raw OPC-UA interface. which offers a raw OPC-UA interface.
""" """
def __init__(self, url, timeout=1): def __init__(self, url, timeout=1, security_policy=ua.SecurityPolicy()):
""" """
used url argument to connect to server. used url argument to connect to server.
if you are unsure of url, write at least hostname and port if you are unsure of url, write at least hostname and port
...@@ -82,8 +82,7 @@ class Client(object): ...@@ -82,8 +82,7 @@ class Client(object):
self.description = self.name self.description = self.name
self.application_uri = "urn:freeopcua:client" self.application_uri = "urn:freeopcua:client"
self.product_uri = "urn:freeopcua.github.no:client" self.product_uri = "urn:freeopcua.github.no:client"
self.security_policy_uri = "http://opcfoundation.org/UA/SecurityPolicy#None" self.security_policy = security_policy
self.security_mode = ua.MessageSecurityMode.None_
self.secure_channel_id = None self.secure_channel_id = None
self.default_timeout = 3600000 self.default_timeout = 3600000
self.secure_channel_timeout = self.default_timeout self.secure_channel_timeout = self.default_timeout
...@@ -92,8 +91,7 @@ class Client(object): ...@@ -92,8 +91,7 @@ class Client(object):
self.server_certificate = b"" self.server_certificate = b""
self.client_certificate = b"" self.client_certificate = b""
self.private_key = b"" self.private_key = b""
self.bclient = BinaryClient(timeout) self.bclient = BinaryClient(timeout, security_policy=security_policy)
self._nonce = None
self._session_counter = 1 self._session_counter = 1
self.keepalive = None self.keepalive = None
...@@ -192,10 +190,12 @@ class Client(object): ...@@ -192,10 +190,12 @@ class Client(object):
params.RequestType = ua.SecurityTokenRequestType.Issue params.RequestType = ua.SecurityTokenRequestType.Issue
if renew: if renew:
params.RequestType = ua.SecurityTokenRequestType.Renew params.RequestType = ua.SecurityTokenRequestType.Renew
params.SecurityMode = self.security_mode params.SecurityMode = self.security_policy.Mode
params.RequestedLifetime = self.secure_channel_timeout params.RequestedLifetime = self.secure_channel_timeout
params.ClientNonce = '\x00' nonce = utils.create_nonce(self.security_policy.symmetric_key_size) # length should be equal to the length of key of symmetric encryption
params.ClientNonce = nonce # this nonce is used to create a symmetric key
result = self.bclient.open_secure_channel(params) result = self.bclient.open_secure_channel(params)
self.security_policy.make_symmetric_key(nonce, result.ServerNonce)
self.secure_channel_timeout = result.SecurityToken.RevisedLifetime self.secure_channel_timeout = result.SecurityToken.RevisedLifetime
def close_secure_channel(self): def close_secure_channel(self):
...@@ -251,18 +251,20 @@ class Client(object): ...@@ -251,18 +251,20 @@ class Client(object):
desc.ApplicationType = ua.ApplicationType.Client desc.ApplicationType = ua.ApplicationType.Client
params = ua.CreateSessionParameters() params = ua.CreateSessionParameters()
params.ClientNonce = utils.create_nonce() nonce = utils.create_nonce(32) # at least 32 random bytes for server to prove possession of private key (specs part 4, 5.6.2.2)
params.ClientCertificate = b'' params.ClientNonce = nonce
params.ClientCertificate = self.security_policy.client_certificate
params.ClientDescription = desc params.ClientDescription = desc
params.EndpointUrl = self.server_url.geturl() params.EndpointUrl = self.server_url.geturl()
params.SessionName = self.description + " Session" + str(self._session_counter) params.SessionName = self.description + " Session" + str(self._session_counter)
params.RequestedSessionTimeout = 3600000 params.RequestedSessionTimeout = 3600000
params.MaxResponseMessageSize = 0 # means no max size params.MaxResponseMessageSize = 0 # means no max size
params.ClientCertificate = self.client_certificate
response = self.bclient.create_session(params) response = self.bclient.create_session(params)
self.security_policy.asymmetric_cryptography.verify(self.security_policy.client_certificate + nonce, response.ServerSignature.Signature)
self._server_nonce = response.ServerNonce
self.server_certificate = response.ServerCertificate self.server_certificate = response.ServerCertificate
for ep in response.ServerEndpoints: for ep in response.ServerEndpoints:
if urlparse(ep.EndpointUrl).scheme == self.server_url.scheme and ep.SecurityMode == self.security_mode: if urlparse(ep.EndpointUrl).scheme == self.server_url.scheme and ep.SecurityMode == self.security_policy.Mode:
# remember PolicyId's: we will use them in activate_session() # remember PolicyId's: we will use them in activate_session()
self._policy_ids = ep.UserIdentityTokens self._policy_ids = ep.UserIdentityTokens
self.session_timeout = response.RevisedSessionTimeout self.session_timeout = response.RevisedSessionTimeout
...@@ -285,6 +287,9 @@ class Client(object): ...@@ -285,6 +287,9 @@ class Client(object):
Activate session using either username and password or private_key Activate session using either username and password or private_key
""" """
params = ua.ActivateSessionParameters() params = ua.ActivateSessionParameters()
challenge = self.security_policy.server_certificate + self._server_nonce
params.ClientSignature.Algorithm = b"http://www.w3.org/2000/09/xmldsig#rsa-sha1"
params.ClientSignature.Signature = self.security_policy.asymmetric_cryptography.signature(challenge)
params.LocaleIds.append("en") params.LocaleIds.append("en")
if not username and not certificate: if not username and not certificate:
params.UserIdentityToken = ua.AnonymousIdentityToken() params.UserIdentityToken = ua.AnonymousIdentityToken()
......
from opcua.uaprotocol import SecurityPolicy, MessageSecurityMode, CryptographyNone
try:
from cryptography import x509, exceptions
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives import hashes,serialization,hmac
cryptography_available = True
except ImportError:
cryptography_available = False
def require_cryptography(obj):
if not cryptography_available:
raise Exception("Can't use {}, cryptography module is not installed".format(obj.__class__.__name__))
class Cryptography(CryptographyNone):
"""
Security policy: Sign or SignAndEncrypt
"""
def __init__(self, mode=MessageSecurityMode.Sign):
self.Signer = None
self.Verifier = None
self.Encryptor = None
self.Decryptor = None
assert(mode in (MessageSecurityMode.Sign, MessageSecurityMode.SignAndEncrypt))
self.is_encrypted = (mode == MessageSecurityMode.SignAndEncrypt)
def plain_block_size(self):
"""
Size of plain text block for block cipher.
"""
if self.is_encrypted:
return self.Encryptor.plain_block_size()
return 1
def encrypted_block_size(self):
"""
Size of encrypted text block for block cipher.
"""
if self.is_encrypted:
return self.Encryptor.encrypted_block_size()
return 1
def padding(self, size):
"""
Create padding for a block of given size.
plain_size = size + len(padding) + signature_size()
plain_size = N * plain_block_size()
"""
if not self.is_encrypted:
return b''
rem = (size + self.signature_size() + 1) % self.Encryptor.plain_block_size()
if rem != 0:
rem = self.Encryptor.plain_block_size() - rem
return bytes(bytearray([rem])) * (rem + 1)
def min_padding_size(self):
if self.is_encrypted:
return 1
return 0
def signature_size(self):
return self.Signer.signature_size()
def signature(self, data):
return self.Signer.signature(data)
def vsignature_size(self):
return self.Verifier.signature_size()
def verify(self, data, sig):
self.Verifier.verify(data, sig)
def encrypt(self, data):
if self.is_encrypted:
assert(len(data) % self.Encryptor.plain_block_size() == 0)
return self.Encryptor.encrypt(data)
return data
def decrypt(self, data):
if self.is_encrypted:
#assert(len(data) % self.Decryptor.encrypted_block_size() == 0)
return self.Decryptor.decrypt(data)
return data
def remove_padding(self, data):
if self.is_encrypted:
pad_size = bytearray(data[-1:])[0] + 1
return data[:-pad_size]
return data
class SignerRsa:
def __init__(self, client_pk):
require_cryptography(self)
self.client_pk = serialization.load_pem_private_key(client_pk, None, default_backend())
self.key_size = self.client_pk.key_size // 8
def signature_size(self):
assert(len(self.signature(b'')) == self.key_size)
return self.key_size
def signature(self, data):
s = self.client_pk.signer(padding.PKCS1v15(), hashes.SHA1())
s.update(data)
return s.finalize()
class VerifierRsa:
def __init__(self, server_cert):
require_cryptography(self)
self.server_cert = x509.load_der_x509_certificate(server_cert, default_backend())
self.key_size = self.server_cert.public_key().key_size // 8
def signature_size(self):
return self.key_size
def verify(self, data, signature):
verifier = self.server_cert.public_key().verifier(signature, padding.PKCS1v15(), hashes.SHA1())
verifier.update(data)
verifier.verify()
class EncryptorRsa:
def __init__(self, server_cert, padding, padding_size):
require_cryptography(self)
self.server_cert = x509.load_der_x509_certificate(server_cert, default_backend())
self.key_size = self.server_cert.public_key().key_size // 8
self.padding = padding
self.padding_size = padding_size
def plain_block_size(self):
return self.key_size - self.padding_size
def encrypted_block_size(self):
return self.key_size
def encrypt(self, data):
e = b''
for i in range(0, len(data), self.plain_block_size()):
e += self.server_cert.public_key().encrypt(data[i:i+self.plain_block_size()], self.padding)
return e
class DecryptorRsa:
def __init__(self, client_pk, padding, padding_size):
require_cryptography(self)
self.client_pk = serialization.load_pem_private_key(client_pk, None, default_backend())
self.key_size = self.client_pk.key_size // 8
self.padding = padding
self.padding_size = padding_size
def plain_block_size(self):
return self.key_size - self.padding_size
def encrypted_block_size(self):
return self.key_size
def decrypt(self, data):
d = b''
for i in range(0, len(data), self.encrypted_block_size()):
d += self.client_pk.decrypt(data[i:i+self.encrypted_block_size()], self.padding)
return d
class SignerAesCbc:
def __init__(self, key):
require_cryptography(self)
self.key = key
def signature_size(self):
return hashes.SHA1.digest_size
def signature(self, data):
h = hmac.HMAC(self.key, hashes.SHA1(), backend=default_backend())
h.update(data)
return h.finalize()
class VerifierAesCbc:
def __init__(self, key):
require_cryptography(self)
self.key = key
def signature_size(self):
return hashes.SHA1.digest_size
def verify(self, data, signature):
h = hmac.HMAC(self.key, hashes.SHA1(), backend=default_backend())
h.update(data)
expected = h.finalize()
if signature != expected:
raise exceptions.InvalidSignature
class EncryptorAesCbc:
def __init__(self, key, iv):
require_cryptography(self)
self.cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend())
def plain_block_size(self):
return self.cipher.algorithm.key_size // 8
def encrypted_block_size(self):
return self.cipher.algorithm.key_size // 8
def encrypt(self, data):
encryptor = self.cipher.encryptor()
return encryptor.update(data) + encryptor.finalize()
class DecryptorAesCbc:
def __init__(self, key, iv):
require_cryptography(self)
self.cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend())
def plain_block_size(self):
return self.cipher.algorithm.key_size // 8
def encrypted_block_size(self):
return self.cipher.algorithm.key_size // 8
def decrypt(self, data):
decryptor = self.cipher.decryptor()
return decryptor.update(data) + decryptor.finalize()
def hash_hmac(a, b):
h = hmac.HMAC(a, hashes.SHA1(), backend=default_backend())
h.update(b)
return h.finalize()
def p_sha1(key, body, sizes=()):
size = 0
for s in sizes:
size += s
result = b''
accum = body
while len(result) < size:
accum = hash_hmac(key, accum)
result += hash_hmac(key, accum + body)
parts = []
for s in sizes:
parts.append(result[:s])
result = result[s:]
return tuple(parts)
class SecurityPolicyBasic128Rsa15(SecurityPolicy):
"""
Security Basic 128Rsa15
A suite of algorithms that uses RSA15 as Key-Wrap-algorithm and 128-Bit for encryption algorithms.
-> SymmetricSignatureAlgorithm – HmacSha1 – (http://www.w3.org/2000/09/xmldsig#hmac-sha1).
-> SymmetricEncryptionAlgorithm – Aes128 – (http://www.w3.org/2001/04/xmlenc#aes128-cbc).
-> AsymmetricSignatureAlgorithm – RsaSha1 – (http://www.w3.org/2000/09/xmldsig#rsa-sha1).
-> AsymmetricKeyWrapAlgorithm – KwRsa15 – (http://www.w3.org/2001/04/xmlenc#rsa-1_5).
-> AsymmetricEncryptionAlgorithm – Rsa15 – (http://www.w3.org/2001/04/xmlenc#rsa-1_5).
-> KeyDerivationAlgorithm – PSha1 – (http://docs.oasis-open.org/ws-sx/ws-secureconversation/200512/dk/p_sha1).
-> DerivedSignatureKeyLength – 128 (16 bytes)
-> MinAsymmetricKeyLength – 1024 (128 bytes)
-> MaxAsymmetricKeyLength – 2048 (256 bytes)
-> CertificateSignatureAlgorithm – Sha1
If a certificate or any certificate in the chain is not signed with a hash that is Sha1 or stronger then the certificate shall be rejected.
"""
URI = "http://opcfoundation.org/UA/SecurityPolicy#Basic128Rsa15"
signature_key_size = 16
symmetric_key_size = 16
def __init__(self, server_cert, client_cert, client_pk, mode=MessageSecurityMode.SignAndEncrypt):
require_cryptography(self)
self.asymmetric_cryptography = Cryptography(MessageSecurityMode.SignAndEncrypt)
self.asymmetric_cryptography.Signer = SignerRsa(client_pk)
self.asymmetric_cryptography.Verifier = VerifierRsa(server_cert)
self.asymmetric_cryptography.Encryptor = EncryptorRsa(server_cert, padding.PKCS1v15(), 11)
self.asymmetric_cryptography.Decryptor = DecryptorRsa(client_pk, padding.PKCS1v15(), 11)
self.symmetric_cryptography = Cryptography(mode)
self.Mode = mode
self.server_certificate = server_cert
self.client_certificate = client_cert
def make_symmetric_key(self, n1, n2):
(sigkey, key, iv) = p_sha1(n2, n1, (self.signature_key_size, self.symmetric_key_size, 16))
self.symmetric_cryptography.Signer = SignerAesCbc(sigkey)
self.symmetric_cryptography.Encryptor = EncryptorAesCbc(key, iv)
(sigkey2, key2, iv2) = p_sha1(n1, n2, (self.signature_key_size, self.symmetric_key_size, 16))
self.symmetric_cryptography.Verifier = VerifierAesCbc(sigkey2)
self.symmetric_cryptography.Decryptor = DecryptorAesCbc(key2, iv2)
def oaep():
return padding.OAEP(padding.MGF1(hashes.SHA1()), hashes.SHA1(), None)
class SecurityPolicyBasic256(SecurityPolicy):
"""
Security Basic 256
A suite of algorithms that are for 256-Bit encryption, algorithms include:
-> SymmetricSignatureAlgorithm – HmacSha1 – (http://www.w3.org/2000/09/xmldsig#hmac-sha1).
-> SymmetricEncryptionAlgorithm – Aes256 – (http://www.w3.org/2001/04/xmlenc#aes256-cbc).
-> AsymmetricSignatureAlgorithm – RsaSha1 – (http://www.w3.org/2000/09/xmldsig#rsa-sha1).
-> AsymmetricKeyWrapAlgorithm – KwRsaOaep – (http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p).
-> AsymmetricEncryptionAlgorithm – RsaOaep – (http://www.w3.org/2001/04/xmlenc#rsa-oaep).
-> KeyDerivationAlgorithm – PSha1 – (http://docs.oasis-open.org/ws-sx/ws-secureconversation/200512/dk/p_sha1).
-> DerivedSignatureKeyLength – 192 (24 bytes)
-> MinAsymmetricKeyLength – 1024 (128 bytes)
-> MaxAsymmetricKeyLength – 2048 (256 bytes)
-> CertificateSignatureAlgorithm – Sha1
If a certificate or any certificate in the chain is not signed with a hash that is Sha1 or stronger then the certificate shall be rejected.
"""
URI = "http://opcfoundation.org/UA/SecurityPolicy#Basic256"
signature_key_size = 24
symmetric_key_size = 32
def __init__(self, server_cert, client_cert, client_pk, mode=MessageSecurityMode.SignAndEncrypt):
require_cryptography(self)
self.asymmetric_cryptography = Cryptography(MessageSecurityMode.SignAndEncrypt)
self.asymmetric_cryptography.Signer = SignerRsa(client_pk)
self.asymmetric_cryptography.Verifier = VerifierRsa(server_cert)
self.asymmetric_cryptography.Encryptor = EncryptorRsa(server_cert, oaep(), 42)
self.asymmetric_cryptography.Decryptor = DecryptorRsa(client_pk, oaep(), 42)
self.symmetric_cryptography = Cryptography(mode)
self.Mode = mode
self.server_certificate = server_cert
self.client_certificate = client_cert
def make_symmetric_key(self, n1, n2):
(sigkey, key, iv) = p_sha1(n2, n1, (self.signature_key_size, self.symmetric_key_size, 16))
self.symmetric_cryptography.Signer = SignerAesCbc(sigkey)
self.symmetric_cryptography.Encryptor = EncryptorAesCbc(key, iv)
(sigkey2, key2, iv2) = p_sha1(n1, n2, (self.signature_key_size, self.symmetric_key_size, 16))
self.symmetric_cryptography.Verifier = VerifierAesCbc(sigkey2)
self.symmetric_cryptography.Decryptor = DecryptorAesCbc(key2, iv2)
...@@ -19,6 +19,7 @@ from opcua import Client ...@@ -19,6 +19,7 @@ from opcua import Client
from opcua import Server from opcua import Server
from opcua import Node from opcua import Node
from opcua import uamethod from opcua import uamethod
from opcua import security_policies
def add_minimum_args(parser): def add_minimum_args(parser):
...@@ -58,7 +59,29 @@ def add_common_args(parser): ...@@ -58,7 +59,29 @@ def add_common_args(parser):
type=int, type=int,
default=0, default=0,
metavar="NAMESPACE") metavar="NAMESPACE")
parser.add_argument("--security",
help="Security settings, for example: Basic256,SignAndEncrypt,cert.der,pk.pem[,server_cert.der]. Default: None",
default='')
def client_security(security, url, timeout):
parts = security.split(',')
if len(parts) < 4:
raise Exception('Wrong format: `{}`, expected at least 4 comma-separated values'.format(security))
policy_class = getattr(security_policies, 'SecurityPolicy' + parts[0])
mode = getattr(ua.MessageSecurityMode, parts[1])
cert = open(parts[2], 'rb').read()
pk = open(parts[3], 'rb').read()
server_cert = None
if len(parts) == 5:
server_cert = open(parts[4], 'rb').read()
else:
# we need server's certificate too. Let's get it from the list of endpoints
client = Client(url, timeout=timeout)
for ep in client.connect_and_get_server_endpoints():
if ep.EndpointUrl.startswith(ua.OPC_TCP_SCHEME) and ep.SecurityMode == mode and ep.SecurityPolicyUri == policy_class.URI:
server_cert = ep.ServerCertificate
return policy_class(server_cert, cert, pk, mode)
def parse_args(parser, requirenodeid=False): def parse_args(parser, requirenodeid=False):
args = parser.parse_args() args = parser.parse_args()
...@@ -66,6 +89,8 @@ def parse_args(parser, requirenodeid=False): ...@@ -66,6 +89,8 @@ def parse_args(parser, requirenodeid=False):
if args.url and '://' not in args.url: if args.url and '://' not in args.url:
logging.info("Adding default scheme %s to URL %s", ua.OPC_TCP_SCHEME, args.url) logging.info("Adding default scheme %s to URL %s", ua.OPC_TCP_SCHEME, args.url)
args.url = ua.OPC_TCP_SCHEME + '://' + args.url args.url = ua.OPC_TCP_SCHEME + '://' + args.url
if hasattr(args, 'security') and args.security:
args.security = client_security(args.security, args.url, args.timeout)
# check that a nodeid has been given explicitly, a bit hackish... # check that a nodeid has been given explicitly, a bit hackish...
if requirenodeid and args.nodeid == "i=84" and args.path == "": if requirenodeid and args.nodeid == "i=84" and args.path == "":
parser.print_usage() parser.print_usage()
...@@ -99,7 +124,7 @@ def uaread(): ...@@ -99,7 +124,7 @@ def uaread():
args = parse_args(parser, requirenodeid=True) args = parse_args(parser, requirenodeid=True)
client = Client(args.url, timeout=args.timeout) client = Client(args.url, timeout=args.timeout, security_policy=args.security)
client.connect() client.connect()
try: try:
node = get_node(client, args) node = get_node(client, args)
...@@ -235,7 +260,7 @@ def uawrite(): ...@@ -235,7 +260,7 @@ def uawrite():
metavar="VALUE") metavar="VALUE")
args = parse_args(parser, requirenodeid=True) args = parse_args(parser, requirenodeid=True)
client = Client(args.url, timeout=args.timeout) client = Client(args.url, timeout=args.timeout, security_policy=args.security)
client.connect() client.connect()
try: try:
node = get_node(client, args) node = get_node(client, args)
...@@ -266,7 +291,7 @@ def uals(): ...@@ -266,7 +291,7 @@ def uals():
if args.long_format is None: if args.long_format is None:
args.long_format = 1 args.long_format = 1
client = Client(args.url, timeout=args.timeout) client = Client(args.url, timeout=args.timeout, security_policy=args.security)
client.connect() client.connect()
try: try:
node = get_node(client, args) node = get_node(client, args)
...@@ -351,7 +376,7 @@ def uasubscribe(): ...@@ -351,7 +376,7 @@ def uasubscribe():
args = parse_args(parser, requirenodeid=True) args = parse_args(parser, requirenodeid=True)
client = Client(args.url, timeout=args.timeout) client = Client(args.url, timeout=args.timeout, security_policy=args.security)
client.connect() client.connect()
try: try:
node = get_node(client, args) node = get_node(client, args)
...@@ -432,7 +457,7 @@ def uaclient(): ...@@ -432,7 +457,7 @@ def uaclient():
help="set client private key") help="set client private key")
args = parse_args(parser) args = parse_args(parser)
client = Client(args.url, timeout=args.timeout) client = Client(args.url, timeout=args.timeout, security_policy=args.security)
client.connect() client.connect()
if args.certificate: if args.certificate:
client.load_client_certificate(args.certificate) client.load_client_certificate(args.certificate)
...@@ -593,7 +618,7 @@ def uahistoryread(): ...@@ -593,7 +618,7 @@ def uahistoryread():
args = parse_args(parser, requirenodeid=True) args = parse_args(parser, requirenodeid=True)
client = Client(args.url, timeout=args.timeout) client = Client(args.url, timeout=args.timeout, security_policy=args.security)
client.connect() client.connect()
try: try:
node = get_node(client, args) node = get_node(client, args)
......
...@@ -254,7 +254,7 @@ class CryptographyNone: ...@@ -254,7 +254,7 @@ class CryptographyNone:
""" """
Base class for symmetric/asymmetric cryprography Base class for symmetric/asymmetric cryprography
""" """
def __init__(self, mode=auto.MessageSecurityMode.None_): def __init__(self):
pass pass
def plain_block_size(self): def plain_block_size(self):
...@@ -309,17 +309,17 @@ class SecurityPolicy: ...@@ -309,17 +309,17 @@ class SecurityPolicy:
""" """
Base class for security policy Base class for security policy
""" """
URI = "http://opcfoundation.org/UA/SecurityPolicy#None"
signature_key_size = 0
symmetric_key_size = 0
def __init__(self): def __init__(self):
self.asymmetric_cryptography = CryptographyNone() self.asymmetric_cryptography = CryptographyNone()
self.symmetric_cryptography = CryptographyNone() self.symmetric_cryptography = CryptographyNone()
self.Mode = auto.MessageSecurityMode.None_ self.Mode = auto.MessageSecurityMode.None_
self.URI = "http://opcfoundation.org/UA/SecurityPolicy#None"
self.server_certificate = b"" self.server_certificate = b""
self.client_certificate = b"" self.client_certificate = b""
def symmetric_key_size(self):
return 0
def make_symmetric_key(self, a, b): def make_symmetric_key(self, a, b):
pass pass
...@@ -433,6 +433,33 @@ class MessageChunk(uatypes.FrozenClass): ...@@ -433,6 +433,33 @@ class MessageChunk(uatypes.FrozenClass):
__repr__ = __str__ __repr__ = __str__
def tcp_message_from_header_and_body(security_policy, header, body):
"""
Convert binary stream to OPC UA TCP message (see OPC UA specs Part 6, 7.1)
The only supported message types are Hello, Acknowledge and Error
"""
if header.MessageType in (MessageType.SecureOpen, MessageType.SecureClose, MessageType.SecureMessage):
return MessageChunk.from_header_and_body(security_policy, header, body)
elif header.MessageType == MessageType.Hello:
return Hello.from_binary(body)
elif header.MessageType == MessageType.Acknowledge:
return Acknowledge.from_binary(body)
elif header.MessageType == MessageType.Error:
return ErrorMessage.from_binary(body)
else:
raise Exception("Unsupported message type {}".format(header.MessageType))
def tcp_message_from_socket(security_policy, socket):
logger.debug("Waiting for header")
header = Header.from_string(socket)
logger.info("received header: %s", header)
body = socket.read(header.body_size)
if len(body) != header.body_size:
raise Exception("{} bytes expected, {} available".format(header.body_size, len(body)))
return tcp_message_from_header_and_body(security_policy, header, utils.Buffer(body))
# FIXES for missing switchfield in NodeAttributes classes # FIXES for missing switchfield in NodeAttributes classes
ana = auto.NodeAttributesMask ana = auto.NodeAttributesMask
......
import logging import logging
import uuid import os
from concurrent.futures import Future from concurrent.futures import Future
import functools import functools
import threading import threading
...@@ -108,8 +108,8 @@ class SocketWrapper(object): ...@@ -108,8 +108,8 @@ class SocketWrapper(object):
def create_nonce(): def create_nonce(size=32):
return uuid.uuid4().bytes + uuid.uuid4().bytes # seems we need at least 32 bytes not 16 as python gives us... return os.urandom(size)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment