You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@plc4x.apache.org by ld...@apache.org on 2024/02/12 09:25:53 UTC

(plc4x) 04/12: Draft - read variable from milo

This is an automated email from the ASF dual-hosted git repository.

ldywicki pushed a commit to branch pg/security-policy
in repository https://gitbox.apache.org/repos/asf/plc4x.git

commit e1cb6b40c7a6f3328981df3988aefad8cc1975ff
Author: PatrykGala <pa...@gmail.com>
AuthorDate: Wed Jul 19 11:33:02 2023 +0200

    Draft - read variable from milo
---
 ...ndler.java => AsymmetricEncryptionHandler.java} |  63 +++++-
 .../java/opcua/context/EncryptionHandler.java      |  36 +++-
 .../plc4x/java/opcua/context/SecureChannel.java    |  13 +-
 .../opcua/context/SymmetricEncryptionHandler.java  | 217 +++++++++++++++++++++
 .../plc4x/java/opcua/context/SymmetricKeys.java    | 121 ++++++++++++
 .../plc4x/java/opcua/OpcuaPlcDriverTest.java       |   6 +-
 6 files changed, 441 insertions(+), 15 deletions(-)

diff --git a/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/context/AsyncEncryptionHandler.java b/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/context/AsymmetricEncryptionHandler.java
similarity index 70%
rename from plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/context/AsyncEncryptionHandler.java
rename to plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/context/AsymmetricEncryptionHandler.java
index 0fbbc932bf..e49802e28b 100644
--- a/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/context/AsyncEncryptionHandler.java
+++ b/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/context/AsymmetricEncryptionHandler.java
@@ -2,6 +2,7 @@ package org.apache.plc4x.java.opcua.context;
 
 import org.apache.plc4x.java.opcua.readwrite.MessagePDU;
 import org.apache.plc4x.java.opcua.readwrite.OpcuaAPU;
+import org.apache.plc4x.java.opcua.readwrite.OpcuaOpenResponse;
 import org.apache.plc4x.java.spi.generation.*;
 import org.eclipse.milo.opcua.stack.core.security.SecurityPolicy;
 
@@ -13,7 +14,7 @@ import java.security.cert.X509Certificate;
 import java.security.interfaces.RSAPublicKey;
 
 
-public class AsyncEncryptionHandler {
+public class AsymmetricEncryptionHandler {
     private static final int SECURE_MESSAGE_HEADER_SIZE = 12;
     private static final int SEQUENCE_HEADER_SIZE = 8;
 
@@ -23,13 +24,11 @@ public class AsyncEncryptionHandler {
     private X509Certificate clientCertificate;
 
     private final PrivateKey clientPrivateKey;
-    private final PublicKey clientPublicKey;
 
-    public AsyncEncryptionHandler(X509Certificate serverCertificate, X509Certificate clientCertificate, PrivateKey clientPrivateKey, PublicKey clientPublicKey) {
+    public AsymmetricEncryptionHandler(X509Certificate serverCertificate, X509Certificate clientCertificate, PrivateKey clientPrivateKey, PublicKey clientPublicKey) {
         this.serverCertificate = serverCertificate;
         this.clientCertificate = clientCertificate;
         this.clientPrivateKey = clientPrivateKey;
-        this.clientPublicKey = clientPublicKey;
     }
 
     /**
@@ -85,18 +84,70 @@ public class AsyncEncryptionHandler {
 
             buf.setPos(SECURE_MESSAGE_HEADER_SIZE + securityHeaderSize);
 
-            int blockCount = (frameSize - buf.getPos()) / plainTextBlockSize;
+            int blockCount = (frameSize - buf.getPos()) / plainTextBlockSize;// -> plainTextContentSize / plainTextBlockSize
 
             byte[] encrypted = encrypt(plainTextBlockSize, securityHeaderSize, frameSize, buf, blockCount);
             buf.writeByteArray(encrypted);
 
-
             return new ReadBufferByteBased(buf.getBytes(), ByteOrder.LITTLE_ENDIAN);
         } catch (Exception e) {
             throw new RuntimeException(e);
         }
     }
 
+    public OpcuaAPU decodeMessage(OpcuaAPU pdu) {
+        MessagePDU message = pdu.getMessage();
+
+        OpcuaOpenResponse a = (OpcuaOpenResponse) message;
+
+
+        int cipherTextBlockSize = (getAsymmetricKeyLength(serverCertificate) + 7) / 8;
+        int signatureSize = (getAsymmetricKeyLength(clientCertificate) + 7) / 8;
+
+        byte[] textMessage = a.getMessage();
+
+
+        int blockCount = (SEQUENCE_HEADER_SIZE + textMessage.length) / cipherTextBlockSize;
+        int plainTextBufferSize = cipherTextBlockSize * blockCount;
+
+
+        try {
+            WriteBufferByteBased buf = new WriteBufferByteBased(pdu.getLengthInBytes(), ByteOrder.LITTLE_ENDIAN);
+            pdu.serialize(buf);
+
+            String transformation = policy.getAsymmetricEncryptionAlgorithm().getTransformation();
+            Cipher cipher = Cipher.getInstance(transformation);
+            cipher.init(Cipher.DECRYPT_MODE, clientPrivateKey);
+
+            ByteBuffer buffer = ByteBuffer.allocate(plainTextBufferSize);
+            byte[] bytes = buf.getBytes(pdu.getLengthInBytes() - plainTextBufferSize, pdu.getLengthInBytes());
+            ByteBuffer originalMessage = ByteBuffer.wrap(bytes);
+
+            for (int blockNumber = 0; blockNumber < blockCount; blockNumber++) {
+                originalMessage.limit(originalMessage.position() + cipherTextBlockSize);
+                cipher.doFinal(originalMessage, buffer);
+            }
+            buffer.flip();
+
+            buf.setPos(pdu.getLengthInBytes() - plainTextBufferSize);
+            buf.writeByteArray(buffer.array());
+
+
+            int frameSize = pdu.getLengthInBytes() - plainTextBufferSize + buffer.limit();
+
+            updateFrameSize(frameSize, buf);
+
+            byte[] decryptedMessage = buf.getBytes(0, frameSize);
+            ReadBuffer readBuffer = new ReadBufferByteBased(decryptedMessage, ByteOrder.LITTLE_ENDIAN);
+            OpcuaAPU opcuaAPU = OpcuaAPU.staticParse(readBuffer, true);
+            return opcuaAPU;
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+
+
+    }
+
     private byte[] encrypt(int plainTextBlockSize, int securityHeaderSize, int frameSize, WriteBufferByteBased buf, int blockCount) throws Exception {
         ByteBuffer buffer = ByteBuffer.allocate(frameSize - buf.getPos());
         ByteBuffer originalMessage = ByteBuffer.wrap(buf.getBytes(SECURE_MESSAGE_HEADER_SIZE + securityHeaderSize, frameSize));
diff --git a/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/context/EncryptionHandler.java b/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/context/EncryptionHandler.java
index 22bd72240e..e8718c43d0 100644
--- a/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/context/EncryptionHandler.java
+++ b/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/context/EncryptionHandler.java
@@ -53,6 +53,11 @@ public class EncryptionHandler {
     private PublicKey clientPublicKey;
     private final String securitypolicy;
 
+    private byte[] clientNonce = null;
+    private byte[] serverNonce = null;
+    private SymmetricEncryptionHandler symmetricEncryptionHandler= new SymmetricEncryptionHandler();
+
+
     public EncryptionHandler(CertificateKeyPair ckp, byte[] senderCertificate, String securityPolicy) {
         if (ckp != null) {
             this.clientPrivateKey = ckp.getKeyPair().getPrivate();
@@ -71,15 +76,33 @@ public class EncryptionHandler {
 
     //https://reference.opcfoundation.org/Core/Part6/v104/docs/6.7
     public ReadBuffer encodeMessage(MessagePDU pdu, byte[] message) {
-        return new AsyncEncryptionHandler(serverCertificate, clientCertificate, clientPrivateKey, clientPublicKey)
-            .encodeMessage(pdu, message);
+        String messageType = pdu.getMessageType();
+        if ("OPN".equals(messageType)) {
+
+            return new AsymmetricEncryptionHandler(serverCertificate, clientCertificate, clientPrivateKey, clientPublicKey)
+                .encodeMessage(pdu, message);
+        } else {
+
+            return symmetricEncryptionHandler
+                .encodeMessage(pdu, message, clientNonce, serverNonce);
+        }
+
     }
 
     public OpcuaAPU decodeMessage(OpcuaAPU pdu) {//TODo patrykg
         LOGGER.info("Decoding Message with Security policy {}", securitypolicy);
+
         switch (securitypolicy) {
             case "None":
                 return pdu;
+            case "Basic128Rsa15":
+                if (pdu.getMessage().getMessageType().equals("OPN")) {
+                    return new AsymmetricEncryptionHandler(serverCertificate, clientCertificate, clientPrivateKey, clientPublicKey)
+                        .decodeMessage(pdu);
+                } else {
+                    return symmetricEncryptionHandler
+                        .decodeMessage(pdu, clientNonce, serverNonce);
+                }
             case "Basic128Sha128":
                 byte[] message;
                 if (pdu.getMessage() instanceof OpcuaOpenResponse) {
@@ -188,4 +211,13 @@ public class EncryptionHandler {
         System.arraycopy(bytes, startPos, data, 0, numBytes);
         return data;
     }
+
+    public void setClientNonce(byte[] clientNonce) {
+        this.clientNonce = clientNonce;
+    }
+
+
+    public void setServerNonce(byte[] serverNonce) {
+        this.serverNonce = serverNonce;
+    }
 }
diff --git a/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/context/SecureChannel.java b/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/context/SecureChannel.java
index b599eaee8d..798089ea25 100644
--- a/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/context/SecureChannel.java
+++ b/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/context/SecureChannel.java
@@ -38,7 +38,6 @@ import java.net.UnknownHostException;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
 import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
 import java.security.cert.CertificateEncodingException;
 import java.time.Duration;
 import java.util.ArrayList;
@@ -92,7 +91,7 @@ public class SecureChannel {
     );
 
     private static final long EPOCH_OFFSET = 116444736000000000L;         //Offset between OPC UA epoch time and linux epoch time.
-    private static final PascalString APPLICATION_URI = new PascalString("urn:apache:plc4x:client");
+    private static final PascalString APPLICATION_URI = new PascalString("urn:apache:plc4x:client"); //todo patrykg change for value from certificate
     private static final PascalString PRODUCT_URI = new PascalString("urn:apache:plc4x:client");
     private static final PascalString APPLICATION_TEXT = new PascalString("OPCUA client for the Apache PLC4X:PLC4J project");
     private static final long DEFAULT_CONNECTION_LIFETIME = 36000000;
@@ -144,6 +143,7 @@ public class SecureChannel {
             //Sender Certificate gets populated during the 'discover' phase when encryption is enabled.
             this.senderCertificate = configuration.getSenderCertificate();
             this.encryptionHandler = new EncryptionHandler(ckp, this.senderCertificate, configuration.getSecurityPolicy());
+
             try {
                 this.publicCertificate = new PascalByteString(ckp.getCertificate().getEncoded().length, ckp.getCertificate().getEncoded());
                 this.isEncrypted = true;
@@ -157,6 +157,7 @@ public class SecureChannel {
             this.thumbprint = NULL_BYTE_STRING;
             this.isEncrypted = false;
         }
+        encryptionHandler.setClientNonce(clientNonce);
         this.keyStoreFile = configuration.getKeyStoreFile();
 
         // Generate a list of endpoints we can use.
@@ -187,6 +188,8 @@ public class SecureChannel {
         final OpcuaAPU apu;
         try {
             if (this.isEncrypted) {
+                encryptionHandler.setServerNonce(senderNonce);
+                encryptionHandler.setClientNonce(clientNonce);
                 apu = OpcuaAPU.staticParse(encryptionHandler.encodeMessage(messageRequest, buffer.getData()), false);
             } else {
                 apu = new OpcuaAPU(messageRequest);
@@ -345,6 +348,8 @@ public class SecureChannel {
                                 OpenSecureChannelResponse openSecureChannelResponse = (OpenSecureChannelResponse) message.getBody();
                                 tokenId.set((int) ((ChannelSecurityToken) openSecureChannelResponse.getSecurityToken()).getTokenId());
                                 channelId.set((int) ((ChannelSecurityToken) openSecureChannelResponse.getSecurityToken()).getChannelId());
+                                this.senderNonce = openSecureChannelResponse.getServerNonce().getStringValue();
+                                this.encryptionHandler.setServerNonce(openSecureChannelResponse.getServerNonce().getStringValue());
                                 onConnectCreateSessionRequest(context);
                             } catch (PlcConnectionException e) {
                                 LOGGER.error("Error occurred while connecting to OPC UA server", e);
@@ -396,7 +401,7 @@ public class SecureChannel {
             this.endpoint,
             new PascalString(sessionName),
             new PascalByteString(clientNonce.length, clientNonce),
-            NULL_BYTE_STRING,
+            publicCertificate,//TODO patrykg set only if securityMode != None
             120000L,
             0L);
 
@@ -499,7 +504,7 @@ public class SecureChannel {
             REQUEST_TIMEOUT_LONG,
             NULL_EXTENSION_OBJECT);
 
-        SignatureData clientSignature = new SignatureData(NULL_STRING, NULL_BYTE_STRING);
+        SignatureData clientSignature = new SignatureData(NULL_STRING, NULL_BYTE_STRING);//todo patrykg add signature - temporary disabled in Milo, sign request
 
         ActivateSessionRequest activateSessionRequest = new ActivateSessionRequest(
             requestHeader,
diff --git a/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/context/SymmetricEncryptionHandler.java b/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/context/SymmetricEncryptionHandler.java
new file mode 100644
index 0000000000..bbae72029d
--- /dev/null
+++ b/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/context/SymmetricEncryptionHandler.java
@@ -0,0 +1,217 @@
+package org.apache.plc4x.java.opcua.context;
+
+import org.apache.plc4x.java.opcua.readwrite.MessagePDU;
+import org.apache.plc4x.java.opcua.readwrite.OpcuaAPU;
+import org.apache.plc4x.java.opcua.readwrite.OpcuaMessageResponse;
+import org.apache.plc4x.java.spi.generation.*;
+import org.eclipse.milo.opcua.stack.core.security.SecurityPolicy;
+
+import javax.crypto.Cipher;
+import javax.crypto.Mac;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+import java.nio.ByteBuffer;
+import java.security.*;
+import java.security.cert.Certificate;
+import java.security.interfaces.RSAPublicKey;
+
+
+public class SymmetricEncryptionHandler {
+    private static final int SECURE_MESSAGE_HEADER_SIZE = 12;
+    private static final int SEQUENCE_HEADER_SIZE = 8;
+    private static final int SYMMETRIC_SECURITY_HEADER_SIZE = 4;
+
+    private final SecurityPolicy policy = SecurityPolicy.Basic128Rsa15;
+
+
+    private SymmetricKeys keys = null;
+
+    public SymmetricEncryptionHandler() {
+
+
+    }
+
+    /**
+     * Docs: https://reference.opcfoundation.org/Core/Part6/v104/docs/6.7
+     *
+     * @param pdu
+     * @param message
+     * @param clientNonce
+     * @param serverNonce
+     * @return
+     */
+    public ReadBuffer encodeMessage(MessagePDU pdu, byte[] message, byte[] clientNonce, byte[] serverNonce) {
+        int unencryptedLength = pdu.getLengthInBytes();
+        int messageLength = message.length;
+
+        int beforeBodyLength = unencryptedLength - messageLength; // message header, security header, sequence header
+
+        int cipherTextBlockSize = 16; // different for aes
+        int plainTextBlockSize = 16;
+        int signatureSize = 20;
+
+
+        int maxChunkSize = 8196;
+        int paddingOverhead = 1; // if cipherTextBlockSize > 256 => 2
+
+
+        int securityHeaderSize = SYMMETRIC_SECURITY_HEADER_SIZE;
+        int maxCipherTextSize = maxChunkSize - securityHeaderSize;
+        int maxCipherTextBlocks = maxCipherTextSize / cipherTextBlockSize;
+        int maxPlainTextSize = maxCipherTextBlocks * plainTextBlockSize;
+        int maxBodySize = maxPlainTextSize - SEQUENCE_HEADER_SIZE - paddingOverhead - signatureSize;
+
+        int bodySize = Math.min(message.length, maxBodySize);
+
+        int plainTextSize = SEQUENCE_HEADER_SIZE + bodySize + paddingOverhead + signatureSize;
+        int remaining = plainTextSize % plainTextBlockSize;
+        int paddingSize = remaining > 0 ? plainTextBlockSize - remaining : 0;
+
+        int plainTextContentSize = SEQUENCE_HEADER_SIZE + bodySize +
+            signatureSize + paddingSize + paddingOverhead;
+
+        int frameSize = SECURE_MESSAGE_HEADER_SIZE + securityHeaderSize +
+            (plainTextContentSize / plainTextBlockSize) * cipherTextBlockSize;
+
+        SymmetricKeys symmetricKeys = getSymmetricKeys(clientNonce, serverNonce);
+
+        try {
+            WriteBufferByteBased buf = new WriteBufferByteBased(frameSize, ByteOrder.LITTLE_ENDIAN);
+            OpcuaAPU opcuaAPU = new OpcuaAPU(pdu);
+            opcuaAPU.serialize(buf);
+
+            writePadding(paddingSize, buf);
+            updateFrameSize(frameSize, buf);
+
+            byte[] sign = sign(buf.getBytes(), symmetricKeys.getClientKeys());
+            buf.writeByteArray(sign);
+
+            buf.setPos(SECURE_MESSAGE_HEADER_SIZE + securityHeaderSize);
+
+            byte[] encrypted = encrypt(securityHeaderSize, frameSize, buf, symmetricKeys);
+            buf.writeByteArray(encrypted);
+
+            return new ReadBufferByteBased(buf.getBytes(), ByteOrder.LITTLE_ENDIAN);
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public OpcuaAPU decodeMessage(OpcuaAPU pdu, byte[] clientNonce, byte[] serverNonce) {
+        MessagePDU message = pdu.getMessage();
+
+        OpcuaMessageResponse a = (OpcuaMessageResponse) message;
+
+
+        int cipherTextBlockSize = 16; // different for aes256
+
+        byte[] textMessage = a.getMessage();
+
+
+        int blockCount = (SEQUENCE_HEADER_SIZE + textMessage.length) / cipherTextBlockSize;
+        int plainTextBufferSize = cipherTextBlockSize * blockCount;
+
+
+        try {
+            WriteBufferByteBased buf = new WriteBufferByteBased(pdu.getLengthInBytes(), ByteOrder.LITTLE_ENDIAN);
+            pdu.serialize(buf);
+
+            String transformation = policy.getSymmetricEncryptionAlgorithm().getTransformation();
+            SymmetricKeys symmetricKeys = getSymmetricKeys(clientNonce, serverNonce);
+            Cipher cipher = getCipher(symmetricKeys.getServerKeys(), transformation, Cipher.DECRYPT_MODE);
+
+            ByteBuffer buffer = ByteBuffer.allocate(plainTextBufferSize);
+            byte[] bytes = buf.getBytes(pdu.getLengthInBytes() - plainTextBufferSize, pdu.getLengthInBytes());
+            ByteBuffer originalMessage = ByteBuffer.wrap(bytes);
+
+
+            cipher.doFinal(originalMessage, buffer);
+
+            buffer.flip();
+
+            buf.setPos(pdu.getLengthInBytes() - plainTextBufferSize);
+            buf.writeByteArray(buffer.array());
+
+
+            int frameSize = pdu.getLengthInBytes() - plainTextBufferSize + buffer.limit();
+
+            updateFrameSize(frameSize, buf);
+
+            byte[] decryptedMessage = buf.getBytes(0, frameSize);
+            ReadBuffer readBuffer = new ReadBufferByteBased(decryptedMessage, ByteOrder.LITTLE_ENDIAN);
+            OpcuaAPU opcuaAPU = OpcuaAPU.staticParse(readBuffer, true);
+            return opcuaAPU;
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+
+
+    }
+
+    private byte[] encrypt(int securityHeaderSize, int frameSize, WriteBufferByteBased buf, SymmetricKeys symmetricKeys) throws Exception {
+        ByteBuffer buffer = ByteBuffer.allocate(frameSize - buf.getPos());
+        ByteBuffer originalMessage = ByteBuffer.wrap(buf.getBytes(SECURE_MESSAGE_HEADER_SIZE + securityHeaderSize, frameSize));
+
+
+        String transformation = policy.getSymmetricEncryptionAlgorithm().getTransformation();
+        Cipher cipher = getCipher(symmetricKeys.getClientKeys(), transformation, Cipher.ENCRYPT_MODE);
+
+
+        cipher.doFinal(originalMessage, buffer);
+
+        return buffer.array();
+    }
+
+    private static Cipher getCipher(SymmetricKeys.Keys symmetricKeys, String transformation, int mode) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException {
+        Cipher cipher = Cipher.getInstance(transformation);
+
+        SecretKeySpec keySpec = new SecretKeySpec(symmetricKeys.getEncryptionKey(), "AES");
+        IvParameterSpec ivSpec = new IvParameterSpec(symmetricKeys.getInitializationVector());
+
+        cipher.init(mode, keySpec, ivSpec);
+        return cipher;
+    }
+
+    private static void updateFrameSize(int frameSize, WriteBufferByteBased buf) throws SerializationException {
+        int initPosition = buf.getPos();
+        buf.setPos(4);
+        buf.writeInt(32, frameSize);
+        buf.setPos(initPosition);
+    }
+
+
+    public byte[] sign(byte[] data, SymmetricKeys.Keys symmetricKeys) {
+        try {
+            Mac signature = Mac.getInstance("HmacSHA1");
+            signature.init(new SecretKeySpec(symmetricKeys.getSignatureKey(), "HmacSHA1"));
+            signature.update(data);
+            return signature.doFinal();
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private void writePadding(int paddingSize, WriteBufferByteBased buffer) throws Exception {
+        buffer.writeByte((byte) paddingSize);
+        for (int i = 0; i < paddingSize; i++) {
+            buffer.writeByte((byte) paddingSize);
+        }
+    }
+
+
+    static int getAsymmetricKeyLength(Certificate certificate) {
+        PublicKey publicKey = certificate != null ?
+            certificate.getPublicKey() : null;
+
+        return (publicKey instanceof RSAPublicKey) ?
+            ((RSAPublicKey) publicKey).getModulus().bitLength() : 0;
+    }
+
+    private SymmetricKeys getSymmetricKeys(byte[] clientNonce, byte[] serverNonce) {
+        if (keys == null) {
+            keys = SymmetricKeys.generateKeyPair(clientNonce, serverNonce);
+        }
+        return keys;
+    }
+}
diff --git a/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/context/SymmetricKeys.java b/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/context/SymmetricKeys.java
new file mode 100644
index 0000000000..eb2b1e2dde
--- /dev/null
+++ b/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/context/SymmetricKeys.java
@@ -0,0 +1,121 @@
+package org.apache.plc4x.java.opcua.context;
+
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+
+public class SymmetricKeys {
+
+
+    private static String KEY_ALGORITHM = "HmacSHA1";
+
+
+    private final Keys clientKeys;
+    private final Keys serverKeys;
+
+
+    public SymmetricKeys(Keys clientKeys, Keys serverKeys) {
+        this.clientKeys = clientKeys;
+        this.serverKeys = serverKeys;
+    }
+
+    public Keys getClientKeys() {
+        return clientKeys;
+    }
+
+    public Keys getServerKeys() {
+        return serverKeys;
+    }
+
+    public static SymmetricKeys generateKeyPair(byte[] clientNonce, byte[] serverNonce) {
+        int signatureKeySize = 16;// different for aes
+        int encryptionKeySize = 16;
+        int cipherTextBlockSize = 16;
+
+
+        byte[] clientSignatureKey = createKey(serverNonce, clientNonce, 0, signatureKeySize);
+        byte[] clientEncryptionKey = createKey(serverNonce, clientNonce, signatureKeySize, encryptionKeySize);
+        byte[] clientInitializationVector = createKey(serverNonce, clientNonce, signatureKeySize + encryptionKeySize, cipherTextBlockSize);
+
+
+        byte[] serverSignatureKey = createKey(clientNonce, serverNonce, 0, signatureKeySize);
+        byte[] serverEncryptionKey = createKey(clientNonce, serverNonce, signatureKeySize, encryptionKeySize);
+        byte[] serverInitializationVector = createKey(clientNonce, serverNonce, signatureKeySize + encryptionKeySize, cipherTextBlockSize);
+
+        return new SymmetricKeys(
+            new Keys(clientSignatureKey, clientEncryptionKey, clientInitializationVector),
+            new Keys(serverSignatureKey, serverEncryptionKey, serverInitializationVector)
+        );
+    }
+
+    private static byte[] createKey(byte[] secret, byte[] seed, int offset, int length) {
+        try {
+            Mac mac = Mac.getInstance(KEY_ALGORITHM);
+
+            byte[] tempBytes = hash(KEY_ALGORITHM, secret, seed, mac, offset + length);
+            byte[] key = new byte[length];
+
+            System.arraycopy(tempBytes, offset, key, 0, key.length);
+
+            return key;
+
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private static byte[] hash(String transformation,
+                               byte[] secret,
+                               byte[] seed,
+                               Mac mac,
+                               int required) throws Exception {
+
+        byte[] out = new byte[required];
+        int offset = 0;
+        int toCopy;
+        byte[] a = seed;
+        byte[] tmp;
+
+        while (required > 0) {
+            SecretKeySpec key = new SecretKeySpec(secret, transformation);
+            mac.init(key);
+            mac.update(a);
+            a = mac.doFinal();
+            mac.reset();
+            mac.init(key);
+            mac.update(a);
+            mac.update(seed);
+            tmp = mac.doFinal();
+            toCopy = Math.min(required, tmp.length);
+            System.arraycopy(tmp, 0, out, offset, toCopy);
+            offset += toCopy;
+            required -= toCopy;
+        }
+
+        return out;
+    }
+
+
+    public static class Keys {
+        private final byte[] signatureKey;
+        private final byte[] encryptionKey;
+        private final byte[] initializationVector;
+
+        public Keys(byte[] signatureKey, byte[] encryptionKey, byte[] initializationVector) {
+            this.signatureKey = signatureKey;
+            this.encryptionKey = encryptionKey;
+            this.initializationVector = initializationVector;
+        }
+
+        public byte[] getSignatureKey() {
+            return signatureKey;
+        }
+
+        public byte[] getEncryptionKey() {
+            return encryptionKey;
+        }
+
+        public byte[] getInitializationVector() {
+            return initializationVector;
+        }
+    }
+}
diff --git a/plc4j/drivers/opcua/src/test/java/org/apache/plc4x/java/opcua/OpcuaPlcDriverTest.java b/plc4j/drivers/opcua/src/test/java/org/apache/plc4x/java/opcua/OpcuaPlcDriverTest.java
index 3dba6eb7f2..ea22e1f368 100644
--- a/plc4j/drivers/opcua/src/test/java/org/apache/plc4x/java/opcua/OpcuaPlcDriverTest.java
+++ b/plc4j/drivers/opcua/src/test/java/org/apache/plc4x/java/opcua/OpcuaPlcDriverTest.java
@@ -252,9 +252,9 @@ public class OpcuaPlcDriverTest {
 //
 //            builder.addTagAddress("DoesNotExists", DOES_NOT_EXIST_IDENTIFIER_READ_WRITE);
 //
-//            PlcReadRequest request = builder.build();
-//            PlcReadResponse response = request.execute().get();
-//            assertThat(response.getResponseCode("Bool")).isEqualTo(PlcResponseCode.OK);
+            PlcReadRequest request = builder.build();
+            PlcReadResponse response = request.execute().get();
+            assertThat(response.getResponseCode("Bool")).isEqualTo(PlcResponseCode.OK);
 //            assertThat(response.getResponseCode("Byte")).isEqualTo(PlcResponseCode.OK);
 //            assertThat(response.getResponseCode("Double")).isEqualTo(PlcResponseCode.OK);
 //            assertThat(response.getResponseCode("Float")).isEqualTo(PlcResponseCode.OK);