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