4 from datetime
import datetime
6 from opcua.ua import uaprotocol_auto
as auto
8 from opcua.ua import ua_binary
as uabin
12 logger = logging.getLogger(
'opcua.uaprotocol')
14 OPC_TCP_SCHEME =
'opc.tcp' 17 class Hello(uatypes.FrozenClass):
35 b.append(uabin.Primitives.String.pack(self.
EndpointUrl))
41 hello.ProtocolVersion = uabin.Primitives.UInt32.unpack(data)
42 hello.ReceiveBufferSize = uabin.Primitives.UInt32.unpack(data)
43 hello.SendBufferSize = uabin.Primitives.UInt32.unpack(data)
44 hello.MaxMessageSize = uabin.Primitives.UInt32.unpack(data)
45 hello.MaxChunkCount = uabin.Primitives.UInt32.unpack(data)
46 hello.EndpointUrl = uabin.Primitives.String.unpack(data)
57 SecureMessage = b
"MSG" 69 def __init__(self, msgType=None, chunkType=None, channelid=0):
84 if self.
MessageType in (MessageType.SecureOpen, MessageType.SecureClose, MessageType.SecureMessage):
86 b.append(uabin.Primitives.UInt32.pack(size))
87 if self.
MessageType in (MessageType.SecureOpen, MessageType.SecureClose, MessageType.SecureMessage):
88 b.append(uabin.Primitives.UInt32.pack(self.
ChannelId))
94 hdr.MessageType, hdr.ChunkType, hdr.packet_size = struct.unpack(
"<3scI", data.read(8))
95 hdr.body_size = hdr.packet_size - 8
96 if hdr.MessageType
in (MessageType.SecureOpen, MessageType.SecureClose, MessageType.SecureMessage):
98 hdr.ChannelId = uabin.Primitives.UInt32.unpack(data)
103 return struct.calcsize(
"<3scII")
106 return "Header(type:{0}, chunk_type:{1}, body_size:{2}, channel:{3})".format(
120 b.append(self.Error.to_binary())
121 b.append(uabin.Primitives.String.pack(self.
Reason))
127 ack.Error = uatypes.StatusCode.from_binary(data)
128 ack.Reason = uabin.Primitives.String.unpack(data)
132 return "MessageAbort(error:{0}, reason:{1})".format(self.
Error, self.
Reason)
158 ack.ProtocolVersion, ack.ReceiveBufferSize, ack.SendBufferSize, ack.MaxMessageSize, ack.MaxChunkCount \
159 = struct.unpack(
"<5I", data.read(20))
181 hdr.SecurityPolicyURI = uabin.Primitives.String.unpack(data)
182 hdr.SenderCertificate = uabin.Primitives.Bytes.unpack(data)
183 hdr.ReceiverCertificateThumbPrint = uabin.Primitives.Bytes.unpack(data)
187 return "{0}(SecurityPolicy:{1}, certificatesize:{2}, receiverCertificatesize:{3} )".format(
202 obj.TokenId = uabin.Primitives.UInt32.unpack(data)
206 return uabin.Primitives.UInt32.pack(self.
TokenId)
210 return struct.calcsize(
"<I")
213 return "{0}(TokenId:{1} )".format(self.__class__.__name__, self.
TokenId)
227 obj.SequenceNumber = uabin.Primitives.UInt32.unpack(data)
228 obj.RequestId = uabin.Primitives.UInt32.unpack(data)
234 b.append(uabin.Primitives.UInt32.pack(self.
RequestId))
239 return struct.calcsize(
"<II")
242 return "{0}(SequenceNumber:{1}, RequestId:{2} )".format(
249 Base class for symmetric/asymmetric cryprography 257 Size of plain text block for block cipher. 263 Size of encrypted text block for block cipher. 269 Create padding for a block of given size. 270 plain_size = size + len(padding) + signature_size() 271 plain_size = N * plain_block_size() 295 Verify signature and raise exception if signature is invalid 305 Base class for security policy 307 URI =
"http://opcfoundation.org/UA/SecurityPolicy#None" 308 signature_key_size = 0
309 symmetric_key_size = 0
314 self.
Mode = auto.MessageSecurityMode.None_
322 class SecurityPolicyFactory(object):
324 Helper class for creating server-side SecurityPolicy. 325 Server has one certificate and private key, but needs a separate 326 SecurityPolicy for every client and client's certificate 329 def __init__(self, cls=SecurityPolicy, mode=auto.MessageSecurityMode.None_,
330 certificate=
None, private_key=
None):
337 return self.cls.URI == uri
and (mode
is None or self.
mode == mode)
340 if self.
cls is SecurityPolicy:
343 return self.
cls(peer_certificate,
350 Message Chunk, as described in OPC UA specs Part 6, 6.7.2. 353 def __init__(self, security_policy, body=b'', msg_type=MessageType.SecureMessage, chunk_type=ChunkType.Single):
355 if msg_type
in (MessageType.SecureMessage, MessageType.SecureClose):
357 elif msg_type == MessageType.SecureOpen:
360 raise UaError(
"Unsupported message type: {0}".format(msg_type))
367 h = Header.from_string(data)
368 return MessageChunk.from_header_and_body(security_policy, h, data)
372 assert len(buf) >= header.body_size,
'Full body expected here' 373 data = buf.copy(header.body_size)
374 buf.skip(header.body_size)
375 if header.MessageType
in (MessageType.SecureMessage, MessageType.SecureClose):
376 security_header = SymmetricAlgorithmHeader.from_binary(data)
377 crypto = security_policy.symmetric_cryptography
378 elif header.MessageType == MessageType.SecureOpen:
379 security_header = AsymmetricAlgorithmHeader.from_binary(data)
380 crypto = security_policy.asymmetric_cryptography
382 raise UaError(
"Unsupported message type: {0}".format(header.MessageType))
384 obj.MessageHeader = header
385 obj.SecurityHeader = security_header
386 decrypted = crypto.decrypt(data.read(len(data)))
387 signature_size = crypto.vsignature_size()
388 if signature_size > 0:
389 signature = decrypted[-signature_size:]
390 decrypted = decrypted[:-signature_size]
391 crypto.verify(obj.MessageHeader.to_binary() + obj.SecurityHeader.to_binary() + decrypted, signature)
392 data = utils.Buffer(crypto.remove_padding(decrypted))
393 obj.SequenceHeader = SequenceHeader.from_binary(data)
394 obj.Body = data.read(len(data))
398 size = plain_size + self._security_policy.signature_size()
399 pbs = self._security_policy.plain_block_size()
400 assert(size % pbs == 0)
401 return size // pbs * self._security_policy.encrypted_block_size()
404 security = self.SecurityHeader.to_binary()
405 encrypted_part = self.SequenceHeader.to_binary() + self.
Body 406 encrypted_part += self._security_policy.padding(len(encrypted_part))
407 self.MessageHeader.body_size = len(security) + self.
encrypted_size(len(encrypted_part))
408 header = self.MessageHeader.to_binary()
409 encrypted_part += self._security_policy.signature(header + security + encrypted_part)
410 return header + security + self._security_policy.encrypt(encrypted_part)
414 max_encrypted_size = max_chunk_size - Header.max_size() - SymmetricAlgorithmHeader.max_size()
415 max_plain_size = (max_encrypted_size // crypto.encrypted_block_size()) * crypto.plain_block_size()
416 return max_plain_size - SequenceHeader.max_size() - crypto.signature_size() - crypto.min_padding_size()
420 message_type=MessageType.SecureMessage, channel_id=1, request_id=1, token_id=1):
422 Pack message body (as binary string) into one or more chunks. 423 Size of each chunk will not exceed max_chunk_size. 424 Returns a list of MessageChunks. SequenceNumber is not initialized here, 425 it must be set by Secure Channel driver. 427 if message_type == MessageType.SecureOpen:
429 chunk =
MessageChunk(security_policy.asymmetric_cryptography, body, message_type, ChunkType.Single)
430 chunk.SecurityHeader.SecurityPolicyURI = security_policy.URI
431 if security_policy.client_certificate:
432 chunk.SecurityHeader.SenderCertificate = security_policy.client_certificate
433 if security_policy.server_certificate:
434 chunk.SecurityHeader.ReceiverCertificateThumbPrint =\
435 hashlib.sha1(security_policy.server_certificate).digest()
436 chunk.MessageHeader.ChannelId = channel_id
437 chunk.SequenceHeader.RequestId = request_id
440 crypto = security_policy.symmetric_cryptography
441 max_size = MessageChunk.max_body_size(crypto, max_chunk_size)
444 for i
in range(0, len(body), max_size):
445 part = body[i:i + max_size]
446 if i + max_size >= len(body):
447 chunk_type = ChunkType.Single
449 chunk_type = ChunkType.Intermediate
450 chunk =
MessageChunk(crypto, part, message_type, chunk_type)
451 chunk.SecurityHeader.TokenId = token_id
452 chunk.MessageHeader.ChannelId = channel_id
453 chunk.SequenceHeader.RequestId = request_id
458 return "{0}({1}, {2}, {3}, {4} bytes)".format(self.__class__.__name__,
470 return self.
_chunks[0].SequenceHeader.RequestId
473 return self.
_chunks[0].SequenceHeader
476 return self.
_chunks[0].SecurityHeader
479 body = b
"".join([c.Body
for c
in self.
_chunks])
480 return utils.Buffer(body)
485 Common logic for client and server 501 Called on client side when getting secure channel data from server 505 def open(self, params, server):
507 called on server side to open secure channel 509 if not self.
_open or params.RequestType == auto.SecurityTokenRequestType.Issue:
511 self.
channel = auto.OpenSecureChannelResult()
512 self.channel.SecurityToken.TokenId = 13
513 self.channel.SecurityToken.ChannelId = server.get_new_channel_id()
514 self.channel.SecurityToken.RevisedLifetime = params.RequestedLifetime
516 self._old_tokens.append(self.channel.SecurityToken.TokenId)
517 self.channel.SecurityToken.TokenId += 1
518 self.channel.SecurityToken.CreatedAt = datetime.utcnow()
519 self.channel.SecurityToken.RevisedLifetime = params.RequestedLifetime
520 self.channel.ServerNonce = utils.create_nonce(self._security_policy.symmetric_key_size)
521 self._security_policy.make_symmetric_key(self.channel.ServerNonce, params.ClientNonce)
532 Set a list of available security policies. 533 Use this in servers with multiple endpoints with different security 539 return policy.URI == uri
and (mode
is None or policy.Mode == mode)
543 if policy.matches(uri, mode):
546 if self._security_policy.URI != uri
or (mode
is not None and 547 self._security_policy.Mode != mode):
548 raise UaError(
"No matching policy: {0}, {1}".format(uri, mode))
552 Convert OPC UA TCP message (see OPC UA specs Part 6, 7.1) to binary. 553 The only supported types are Hello, Acknowledge and ErrorMessage 555 header =
Header(message_type, ChunkType.Single)
556 binmsg = message.to_binary()
557 header.body_size = len(binmsg)
558 return header.to_binary() + binmsg
560 def message_to_binary(self, message, message_type=MessageType.SecureMessage, request_id=0, algohdr=None):
562 Convert OPC UA secure message to binary. 563 The only supported types are SecureOpen, SecureMessage, SecureClose 564 if message_type is SecureMessage, the AlgoritmHeader should be passed as arg 567 token_id = self.channel.SecurityToken.TokenId
569 token_id = algohdr.TokenId
570 chunks = MessageChunk.message_to_chunks(
572 message_type=message_type,
573 channel_id=self.channel.SecurityToken.ChannelId,
574 request_id=request_id,
582 return b
"".join([chunk.to_binary()
for chunk
in chunks])
585 assert isinstance(chunk, MessageChunk),
"Expected chunk, got: {0}".format(chunk)
586 if chunk.MessageHeader.MessageType != MessageType.SecureOpen:
587 if chunk.MessageHeader.ChannelId != self.channel.SecurityToken.ChannelId:
588 raise UaError(
"Wrong channel id {0}, expected {1}".format(
589 chunk.MessageHeader.ChannelId,
590 self.channel.SecurityToken.ChannelId))
591 if chunk.SecurityHeader.TokenId != self.channel.SecurityToken.TokenId:
592 if chunk.SecurityHeader.TokenId
not in self.
_old_tokens:
593 logger.warning(
"Received a chunk with wrong token id %s, expected %s", chunk.SecurityHeader.TokenId, self.channel.SecurityToken.TokenId)
602 idx = self._old_tokens.index(chunk.SecurityHeader.TokenId)
606 if self.
_incoming_parts[0].SequenceHeader.RequestId != chunk.SequenceHeader.RequestId:
607 raise UaError(
"Wrong request id {0}, expected {1}".format(
608 chunk.SequenceHeader.RequestId,
612 num = chunk.SequenceHeader.SequenceNumber
615 wrap = (1 << 32) - 1024
618 logger.debug(
"Sequence number wrapped: %d -> %d",
622 "Wrong sequence {0} -> {1} (server bug or replay attack)" 628 Convert MessageHeader and binary body to OPC UA TCP message (see OPC UA 629 specs Part 6, 7.1: Hello, Acknowledge or ErrorMessage), or a Message 630 object, or None (if intermediate chunk is received) 632 if header.MessageType == MessageType.SecureOpen:
633 data = body.copy(header.body_size)
634 security_header = AsymmetricAlgorithmHeader.from_binary(data)
635 self.
select_policy(security_header.SecurityPolicyURI, security_header.SenderCertificate)
637 if header.MessageType
in (MessageType.SecureMessage,
638 MessageType.SecureOpen,
639 MessageType.SecureClose):
643 elif header.MessageType == MessageType.Hello:
644 msg = Hello.from_binary(body)
647 elif header.MessageType == MessageType.Acknowledge:
648 msg = Acknowledge.from_binary(body)
651 elif header.MessageType == MessageType.Error:
652 msg = ErrorMessage.from_binary(body)
653 logger.warning(
"Received an error: %s", msg)
656 raise UaError(
"Unsupported message type {0}".format(header.MessageType))
660 Convert binary stream to OPC UA TCP message (see OPC UA 661 specs Part 6, 7.1: Hello, Acknowledge or ErrorMessage), or a Message 662 object, or None (if intermediate chunk is received) 664 logger.debug(
"Waiting for header")
665 header = Header.from_string(socket)
666 logger.info(
"received header: %s", header)
667 body = socket.read(header.body_size)
668 if len(body) != header.body_size:
669 raise UaError(
"{0} bytes expected, {1} available".format(header.body_size, len(body)))
674 self._incoming_parts.append(msg)
675 if msg.MessageHeader.ChunkType == ChunkType.Intermediate:
677 if msg.MessageHeader.ChunkType == ChunkType.Abort:
678 err = ErrorMessage.from_binary(utils.Buffer(msg.Body))
679 logger.warning(
"Message %s aborted: %s", msg, err)
684 elif msg.MessageHeader.ChunkType == ChunkType.Single:
689 raise UaError(
"Unsupported chunk type: {0}".format(msg))
693 ana = auto.NodeAttributesMask
699 auto.ObjectAttributes.__init__(self)
700 self.
SpecifiedAttributes = ana.DisplayName | ana.Description | ana.WriteMask | ana.UserWriteMask | ana.EventNotifier
706 auto.ObjectTypeAttributes.__init__(self)
707 self.
SpecifiedAttributes = ana.DisplayName | ana.Description | ana.WriteMask | ana.UserWriteMask | ana.IsAbstract
713 auto.VariableAttributes.__init__(self)
714 self.
SpecifiedAttributes = ana.DisplayName | ana.Description | ana.WriteMask | ana.UserWriteMask | ana.Value | ana.DataType | ana.ValueRank | ana.ArrayDimensions | ana.AccessLevel | ana.UserAccessLevel | ana.MinimumSamplingInterval | ana.Historizing
721 auto.VariableTypeAttributes.__init__(self)
722 self.
SpecifiedAttributes = ana.DisplayName | ana.Description | ana.WriteMask | ana.UserWriteMask | ana.Value | ana.DataType | ana.ValueRank | ana.ArrayDimensions | ana.IsAbstract
728 auto.MethodAttributes.__init__(self)
729 self.
SpecifiedAttributes = ana.DisplayName | ana.Description | ana.WriteMask | ana.UserWriteMask | ana.Executable | ana.UserExecutable
735 auto.ReferenceTypeAttributes.__init__(self)
736 self.
SpecifiedAttributes = ana.DisplayName | ana.Description | ana.WriteMask | ana.UserWriteMask | ana.IsAbstract | ana.Symmetric | ana.InverseName
742 auto.DataTypeAttributes.__init__(self)
743 self.
SpecifiedAttributes = ana.DisplayName | ana.Description | ana.WriteMask | ana.UserWriteMask | ana.IsAbstract
749 auto.ViewAttributes.__init__(self)
750 self.
SpecifiedAttributes = ana.DisplayName | ana.Description | ana.WriteMask | ana.UserWriteMask | ana.ContainsNoLoops | ana.EventNotifier
756 auto.Argument.__init__(self)
def signature(self, data)
def tcp_to_binary(self, message_type, message)
def receive_from_header_and_body(self, header, body)
def max_body_size(crypto, max_chunk_size)
def select_policy(self, uri, peer_certificate, mode=None)
def open(self, params, server)
def matches(self, uri, mode=None)
def __init__(self, chunks)
def set_channel(self, channel)
def encrypted_block_size(self)
def verify(self, data, signature)
def _check_incoming_chunk(self, chunk)
def min_padding_size(self)
def set_policy_factories(self, policies)
ReceiverCertificateThumbPrint
def remove_padding(self, data)
def vsignature_size(self)
def receive_from_socket(self, socket)
def create(self, peer_certificate)
def __init__(self, cls=SecurityPolicy, mode=auto.MessageSecurityMode.None_, certificate=None, private_key=None)
def plain_block_size(self)
def __init__(self, security_policy)
def _policy_matches(policy, uri, mode=None)
def message_to_binary(self, message, message_type=MessageType.SecureMessage, request_id=0, algohdr=None)
def from_binary(security_policy, data)
def from_header_and_body(security_policy, header, buf)
def encrypted_size(self, plain_size)
def __init__(self, msgType=None, chunkType=None, channelid=0)
def make_symmetric_key(self, a, b)
def __init__(self, security_policy, body=b'', msg_type=MessageType.SecureMessage, chunk_type=ChunkType.Single)
def message_to_chunks(security_policy, body, max_chunk_size, message_type=MessageType.SecureMessage, channel_id=1, request_id=1, token_id=1)