Menu

Open Sesame! Log in to Edit

Jan Costermans
Attachments

"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.

~ Wikipedia


Contents:

  1. python snippet
  2. NodeIds
  3. openSSL
  4. OPC ua timeformat
  5. signature and padding

1. Python snippet

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.

  • MessageType: The first 3 bytes should say "OPN".
  • IsFinal: an ASCII letter. "F" for Final chunk, otherwise "C" for Chunk
  • MessageSize: the size of the entire message (without padding and signature)

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).

  • SecureChannelId: this is set by server, and should be just 0 in the OpenSecureChannel request.
  • SecurtiyPolicyUri: This identifies the type of security that is applied to the message (chunk). This can be None, Basic128Rsa15, ... .
  • SenderCertificate: The X509v3 certificate of the client, DER encoded.
  • ReceiverCertificateThumbprint: The SHA1 digest of the X509v3 certificate of the server, DER encoded.

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:

  • SequenceNumber: this number is incremented by the client all the time.

We just keep concatenating chunks with increasing SequenceNumber until we find a final chunk (with IsFinal = "F").

The next Byte is another number:

  • RequestId: This number is used to map a certain response to a certain request. The server just needs to repeat this Id when sending the response to the request.

Next comes the actual OpenSecureChannel request message.


TO BE CONTINUED ...

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()

2. NodeIds

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:

  • Wireshark decodes the namespace byte of the NodeId as 'EncodingMask'.
  • This ExtensionObject prefix does not contain an encoding mask or length.

3. openSSL

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.

~ http://www.openssl.org/


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()

4. OPC ua timeformat

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).

Conversion

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:


Related

Wiki: Home
Wiki: uaArticles

MongoDB Logo MongoDB