"Open Sesame" (Arabic إفتح يا سمسم iftaḥ ya simsim 'open, O sesame') is a magical phrase in the story of "Ali Baba and the Forty Thieves" in One Thousand and One Nights. It opens the mouth of a cave in which forty thieves have hidden a treasure; "Close Sesame" re-seals the cave.
Let's examine the OpenSecureChannel request 's message structure. Imagine you are an OPC ua server, getting a request.
The first 8 bytes are very similar to the Hello message.
These 8 bytes will make it easy for the communication stack to retrieve the message (chunks) from the TCP stream.
Once we have a message (or a chunk), we want to know how to decrypt it. We can figure this out with the next bytes in the message (chunk).
We were able to decrypt the message (chunk) using the public key of the client. So we now have a message. Or (probably) a bunch of chunks. To reassemble the message from the chunks we need to know the correct order. The next byte comes to the rescue:
We just keep concatenating chunks with increasing SequenceNumber until we find a final chunk (with IsFinal = "F").
The next Byte is another number:
Next comes the actual OpenSecureChannel request message.
import socket
import sys
from construct import *
c = Struct('OPC UASC OpenSecureChannel Request',
String('MessageType', 3),
String('IsFinal', 1),
ULInt32('MessageSize'),
ULInt32('SecureChannelId'),
PascalString('SecurityPolicyUri',
length_field=ULInt32('length')),
SLInt32('SenderCertificateLength'),
Bytes('SenderCertificate',
lambda ctx:ctx['SenderCertificateLength']),
SLInt32('ReceiverCertificateThumbprintLength'),
Bytes('ReceiverCertificateThumbprint',
lambda ctx:ctx['ReceiverCertificateThumbprintLength']),
ULInt32('SequenceNumber'),
ULInt32('RequestId'),
ULInt8('EncodingByte'),
ULInt8('NameSpace'),
ULInt16('Identifier'),
ULInt8('AuthenticationTokenEncodingByte'),
ULInt8('AuthenticationTokenIdentifier'),
ULInt64('TimeStamp'),
ULInt32('RequestHandle'),
ULInt32('ReturnDiagnostics'),
PascalString('AuditEntryId',
length_field=ULInt32('length'),
encoding='utf8'),
ULInt32('TimeOutHint'),
ULInt8('AHEncodingByte'), # AH = AdditionalHeader
ULInt8('AHIdentifier'),
ULInt8('AHEncoding'),
ULInt32('ProtocolVersion'),
ULInt32('RequestType'),
ULInt32('SecurityMode'),
ULInt32('ClientNonce'),
ULInt32('RequestedLifetime'),
)
x = Container(
MessageType = 'OPN' ,
IsFinal = 'F' ,
MessageSize = 154 ,
SecureChannelId = 0 ,
SecurityPolicyUri =
'http://opcfoundation.org/UA/securitypolicy#None',
SenderCertificateLength = 0, #len(dercert),
SenderCertificate = '', #dercert,
ReceiverCertificateThumbprintLength = 0, #len(ThumbPrint),
ReceiverCertificateThumbprint = '', #ThumbPrint,
SequenceNumber = 0 ,
RequestId = 0 ,
EncodingByte = 1 ,
NameSpace = 0 ,
Identifier = 446 ,
AuthenticationTokenEncodingByte = 0,
AuthenticationTokenIdentifier = 0,
TimeStamp = 129839102184810000,
RequestHandle = 1,
ReturnDiagnostics = 0,
AuditEntryId = u'This is an audit entry' ,
TimeOutHint = 1000 ,
AHEncodingByte = 0 , # 2-byte NodeId
AHIdentifier = 0 , # placeholder extensionObject
AHEncoding = 0 , # the extensionObject has no body
ProtocolVersion = 0 ,
RequestType = 0 , # 0 = ISSUE NEW
SecurityMode = 1 , # 1 = NONE
ClientNonce = 0 , #bytestring !!
RequestedLifetime = 60000,
)
packed_data = c.build(x)
print len(packed_data)
# Create a TCP/IP socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_address = ('localhost', 4840)
sock.connect(server_address)
try:
# Send data
print >>sys.stderr, 'sending "%s"' % packed_data
sock.sendall(packed_data)
finally:
print >>sys.stderr, 'closing socket'
sock.close()
Identifier = 446 ,
OPC ua message are encoded as an ExtensionObject. The NodeId identifier is used to identify the type of message. The Identifier for the OpenSecureChannel request message is 446. An overview of all identifiers can be found in NodeIds.csv : http://www.opcfoundation.org/UAPart6/NodeIds.csv.
Some remarks about the ExtensionObject prefix of the OpenSecureChannel message:
The OpenSSL Project is a collaborative effort to develop a robust, commercial-grade, full-featured, and Open Source toolkit implementing the Secure Sockets Layer (SSL v2/v3) and Transport Layer Security (TLS v1) protocols as well as a full-strength general purpose cryptography library.
We can use the openSSL command line tool to generate keys and certificates.
The x509 utility can be used to sign certificates and requests: it can thus behave like a ``mini CA''.
Find detailed documentation over here:
Create a new private key (without a password) :
openssl genrsa -out privatekey.pem 2048
Create a self-signed X509 certificate with the key:
openssl req -new -x509 -key privatekey.pem -out opycua.pem -days 1
Convert the PEM encoded certificate to a DER encoded certificate:
openssl x509 -in opycua.pem -outform DER -out opycua.der
Create a Thumbprint (SHA1 digest) from the DER encoded certificate
openssl x509 -sha1 -in opycua.der -noout -fingerprint
We can also call some of these function from within python like so:
import ssl
import hashlib
f = open('opycua.pem', 'r')
pemcert = f.read()
dercert = ssl.PEM_cert_to_DER_cert(pemcert)
m = hashlib.sha1(dercert)
thumbprint = m.hexdigest()
OPC ua uses a 64-bit time with 100 ns precision.
a 64-bit value representing the number of 100-nanosecond intervals since January 1, 1601 (UTC).
Python uses POSIX time aka Unix time.
a 32-bit value representing the number of seconds that have elapsed since January 1, 1970 (UTC).
opcua_time = (unix_time * 10000000) + 116444736000000000
116444736000000000 = 10000000 * 60 * 60 * 24 * 365 * 369 + 89 leap days
In python, we can do this:
#!/usr/bin/python
import time
def pytime_to_uatime(pytime):
uatime = long((10000000 * pytime) + 116444736000000000)
return uatime
def uatime_to_pytime(uatime):
pytime = float(uatime - 116444736000000000) / 10000000
return pytime
def main():
pytime = time.time()
print "Python: It 's ... "
print time.strftime('%Y-%m-%d %H:%M:%S')
print pytime
uatime = pytime_to_uatime(pytime)
print uatime
print uatime_to_pytime(uatime)
if __name__ == "__main__":main()
Some related articles are over here: