You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@plc4x.apache.org by hu...@apache.org on 2021/01/15 11:56:27 UTC
[plc4x] branch feature/native_opua_client updated: Add support for
User/Pass authentication
This is an automated email from the ASF dual-hosted git repository.
hutcheb pushed a commit to branch feature/native_opua_client
in repository https://gitbox.apache.org/repos/asf/plc4x.git
The following commit(s) were added to refs/heads/feature/native_opua_client by this push:
new 23f7521 Add support for User/Pass authentication
23f7521 is described below
commit 23f7521e3252295e5fa5d064cc9705e4721454f5
Author: hutcheb <be...@gmail.com>
AuthorDate: Fri Jan 15 06:54:40 2021 -0500
Add support for User/Pass authentication
Add some supporting code for encrypted sessions.
Still need to add other encryption methods.
---
.../apache/plc4x/java/opcua/OpcuaPlcDriver.java | 2 +-
.../java/opcua/protocol/OpcuaProtocolLogic.java | 191 ++++++++++++++-------
protocols/opcua/src/main/xslt/opc-types.xsl | 2 +-
3 files changed, 133 insertions(+), 62 deletions(-)
diff --git a/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/OpcuaPlcDriver.java b/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/OpcuaPlcDriver.java
index b100a00..34ac6e8 100644
--- a/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/OpcuaPlcDriver.java
+++ b/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/OpcuaPlcDriver.java
@@ -64,7 +64,7 @@ public class OpcuaPlcDriver extends GeneratedDriverBase<OpcuaAPU> {
public static final Pattern URI_PATTERN = Pattern.compile("^(?<protocolCode>opcua)" +
INET_ADDRESS_PATTERN +
- "(?<transportEndpoint>[\\w/=]+)[\\?]?" +
+ "(?<transportEndpoint>[\\w/=]*)[\\?]?" +
"(?<paramString>[\\&\\w=]+\\=[\\w&]+)*"
);
diff --git a/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/protocol/OpcuaProtocolLogic.java b/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/protocol/OpcuaProtocolLogic.java
index a8d0972..cfe61c6 100644
--- a/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/protocol/OpcuaProtocolLogic.java
+++ b/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/protocol/OpcuaProtocolLogic.java
@@ -65,6 +65,8 @@ import org.slf4j.LoggerFactory;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.time.*;
@@ -137,6 +139,7 @@ public class OpcuaProtocolLogic extends Plc4xProtocolBase<OpcuaAPU> implements H
private final byte[] clientNonce = RandomUtils.nextBytes(40);
private RequestTransactionManager tm;
+ private PascalString policyId = null;
private String endpoint;
private boolean discovery;
private String username;
@@ -149,6 +152,7 @@ public class OpcuaProtocolLogic extends Plc4xProtocolBase<OpcuaAPU> implements H
private AtomicInteger tokenId = new AtomicInteger(1);
private AtomicInteger channelId = new AtomicInteger(1);
private byte[] senderCertificate = null;
+ private byte[] senderNonce = null;
private String certificateThumbprint = null;
private AtomicBoolean securedConnection = new AtomicBoolean(false);
@@ -331,11 +335,16 @@ public class OpcuaProtocolLogic extends Plc4xProtocolBase<OpcuaAPU> implements H
.check(p -> p.getMessage() instanceof OpcuaOpenResponse)
.unwrap(p -> (OpcuaOpenResponse) p.getMessage())
.handle(opcuaOpenResponse -> {
- LOGGER.debug("Got Secure Response Connection Response");
- try {
- onConnectCreateSessionRequest(context, opcuaOpenResponse);
- } catch (PlcConnectionException e) {
- LOGGER.error("Error occurred while connecting to OPC UA server");
+ if (opcuaOpenResponse.getMessage() instanceof ServiceFault) {
+ ServiceFault fault = (ServiceFault) opcuaOpenResponse.getMessage();
+ LOGGER.error("Failed to connect to opc ua server for the following reason:- {}, {}", fault.getResponseHeader().getServiceResult().getStatusCode(), OpcuaStatusCodes.enumForValue(fault.getResponseHeader().getServiceResult().getStatusCode()));
+ } else {
+ LOGGER.debug("Got Secure Response Connection Response");
+ try {
+ onConnectCreateSessionRequest(context, opcuaOpenResponse);
+ } catch (PlcConnectionException e) {
+ LOGGER.error("Error occurred while connecting to OPC UA server");
+ }
}
});
@@ -411,11 +420,16 @@ public class OpcuaProtocolLogic extends Plc4xProtocolBase<OpcuaAPU> implements H
.check(p -> p.getMessage() instanceof OpcuaMessageResponse)
.unwrap(p -> (OpcuaMessageResponse) p.getMessage())
.handle(opcuaMessageResponse -> {
- LOGGER.debug("Got Create Session Response Connection Response");
- try {
- onConnectActivateSessionRequest(context, opcuaMessageResponse);
- } catch (PlcConnectionException e) {
- LOGGER.error("Error occurred while connecting to OPC UA server");
+ if (opcuaMessageResponse.getMessage() instanceof ServiceFault) {
+ ServiceFault fault = (ServiceFault) opcuaMessageResponse.getMessage();
+ LOGGER.error("Failed to connect to opc ua server for the following reason:- {}, {}", fault.getResponseHeader().getServiceResult().getStatusCode(), OpcuaStatusCodes.enumForValue(fault.getResponseHeader().getServiceResult().getStatusCode()));
+ } else {
+ LOGGER.debug("Got Create Session Response Connection Response");
+ try {
+ onConnectActivateSessionRequest(context, opcuaMessageResponse);
+ } catch (PlcConnectionException e) {
+ LOGGER.error("Error occurred while connecting to OPC UA server");
+ }
}
});
}
@@ -424,7 +438,27 @@ public class OpcuaProtocolLogic extends Plc4xProtocolBase<OpcuaAPU> implements H
CreateSessionResponse createSessionResponse = (CreateSessionResponse) opcuaMessageResponse.getMessage();
senderCertificate = createSessionResponse.getServerCertificate().getStringValue();
- authenticationToken = (NodeIdByteString) createSessionResponse.getAuthenticationToken();
+ senderNonce = createSessionResponse.getServerNonce().getStringValue();
+
+ for (EndpointDescription endpointDescription: createSessionResponse.getServerEndpoints()) {
+ LOGGER.info("{} - {}", endpointDescription.getEndpointUrl().getStringValue(), this.endpoint);
+ if (endpointDescription.getEndpointUrl().getStringValue().equals(this.endpoint)) {
+ for (UserTokenPolicy identityToken : endpointDescription.getUserIdentityTokens()) {
+ if (identityToken.getTokenType() == UserTokenType.userTokenTypeAnonymous) {
+ if (this.username == null) {
+ policyId = identityToken.getPolicyId();
+ }
+ } else if (identityToken.getTokenType() == UserTokenType.userTokenTypeUserName) {
+ if (this.username != null) {
+ policyId = identityToken.getPolicyId();
+ }
+ }
+ }
+ }
+ }
+ LOGGER.info(policyId.getStringValue());
+
+ authenticationToken = createSessionResponse.getAuthenticationToken();
tokenId.set((int) opcuaMessageResponse.getSecureTokenId());
channelId.set((int) opcuaMessageResponse.getSecureChannelId());
@@ -455,46 +489,11 @@ public class OpcuaProtocolLogic extends Plc4xProtocolBase<OpcuaAPU> implements H
signedSoftwareCertificate[0] = new SignedSoftwareCertificate(NULL_BYTE_STRING, NULL_BYTE_STRING);
- ExpandedNodeId extExpandedNodeId = null;
ExtensionObject userIdentityToken = null;
if (this.username == null) {
- //Manually serialize this object
- PascalString anonymousIdentityToken = new PascalString("anonymous".length(), "anonymous");
- WriteBuffer buffer = new WriteBuffer(anonymousIdentityToken.getLengthInBytes(), true);
- try{
- PascalStringIO.staticSerialize(buffer, anonymousIdentityToken);
- } catch (ParseException e) {
- LOGGER.error("Failed to serialize the user identity token - {}", anonymousIdentityToken.getStringValue());
- throw new PlcConnectionException("Failed to serialize the user identity token - " + anonymousIdentityToken.getStringValue());
- }
- extExpandedNodeId = new ExpandedNodeIdFourByte(false,
- false,
- null,
- null,
- new FourByteNodeId((short) 0, OpcuaNodeIdServices.AnonymousIdentityToken_Encoding_DefaultBinary.getValue()));
- userIdentityToken = new ExtensionObject(extExpandedNodeId, (short) 1, buffer.getData().length, buffer.getData());
+ userIdentityToken = getIdentityToken("none");
} else {
- //Manually serialize this object
- byte[] encryptedPassword = encrypt(this.password, senderCertificate);
- UserNameIdentityToken userNameIdentityToken = new UserNameIdentityToken(
- new PascalString("username".length(), "username"),
- new PascalString(this.username.length(), this.username),
- new PascalByteString(encryptedPassword.length, encryptedPassword),
- new PascalString(PASSWORD_ENCRYPTION_ALGORITHM.length(), PASSWORD_ENCRYPTION_ALGORITHM)
- );
- WriteBuffer buffer = new WriteBuffer(userNameIdentityToken.getLengthInBytes(), true);
- try{
- UserNameIdentityTokenIO.staticSerialize(buffer, userNameIdentityToken);
- } catch (ParseException e) {
- LOGGER.error("Failed to serialize the user identity token - {}", userNameIdentityToken);
- throw new PlcConnectionException("Failed to serialize the user identity token - " + userNameIdentityToken);
- }
- extExpandedNodeId = new ExpandedNodeIdFourByte(false,
- false,
- null,
- null,
- new FourByteNodeId((short) 0, OpcuaNodeIdServices.UserNameIdentityToken_Encoding_DefaultBinary.getValue()));
- userIdentityToken = new ExtensionObject(extExpandedNodeId, (short) 1, buffer.getData().length, buffer.getData());
+ userIdentityToken = getIdentityToken("username");
}
ActivateSessionRequest activateSessionRequest = new ActivateSessionRequest((byte) 1,
@@ -521,16 +520,20 @@ public class OpcuaProtocolLogic extends Plc4xProtocolBase<OpcuaAPU> implements H
.unwrap(p -> (OpcuaMessageResponse) p.getMessage())
.handle(opcuaActivateResponse -> {
LOGGER.debug("Got Activate Session Response Connection Response");
+ if (opcuaActivateResponse.getMessage() instanceof ServiceFault) {
+ ServiceFault fault = (ServiceFault) opcuaActivateResponse.getMessage();
+ LOGGER.error("Failed to connect to opc ua server for the following reason:- {}, {}", fault.getResponseHeader().getServiceResult().getStatusCode(), OpcuaStatusCodes.enumForValue(fault.getResponseHeader().getServiceResult().getStatusCode()));
+ } else {
+ ActivateSessionResponse activateMessageResponse = (ActivateSessionResponse) opcuaActivateResponse.getMessage();
- ActivateSessionResponse activateMessageResponse = (ActivateSessionResponse) opcuaActivateResponse.getMessage();
+ long returnedRequestHandle = activateMessageResponse.getResponseHeader().getRequestHandle();
+ if (!(requestHandle == returnedRequestHandle)) {
+ LOGGER.error("Request handle isn't as expected, we might have missed a packet. {} != {}" ,requestHandle, returnedRequestHandle);
+ }
- long returnedRequestHandle = activateMessageResponse.getResponseHeader().getRequestHandle();
- if (!(requestHandle == returnedRequestHandle)) {
- LOGGER.error("Request handle isn't as expected, we might have missed a packet. {} != {}" ,requestHandle, returnedRequestHandle);
+ // Send an event that connection setup is complete.
+ context.fireConnected();
}
-
- // Send an event that connection setup is complete.
- context.fireConnected();
});
}
@@ -563,7 +566,7 @@ public class OpcuaProtocolLogic extends Plc4xProtocolBase<OpcuaAPU> implements H
} else if (field.getIdentifierType() == OpcuaIdentifierType.NUMBER_IDENTIFIER) {
nodeId = new NodeIdNumeric(NodeIdType.nodeIdTypeNumeric, new NumericNodeId(field.getNamespace(),Long.valueOf(field.getIdentifier())));
} else if (field.getIdentifierType() == OpcuaIdentifierType.GUID_IDENTIFIER) {
- nodeId = new NodeIdGuid(NodeIdType.nodeIdTypeGuid, new GuidNodeId(field.getNamespace(), field.getIdentifier()));
+ nodeId = new NodeIdGuid(NodeIdType.nodeIdTypeGuid, new GuidNodeId(field.getNamespace(), toGuidValue(field.getIdentifier())));
} else if (field.getIdentifierType() == OpcuaIdentifierType.STRING_IDENTIFIER) {
nodeId = new NodeIdString(NodeIdType.nodeIdTypeString, new StringNodeId(field.getNamespace(), new PascalString(field.getIdentifier().length(), field.getIdentifier())));
}
@@ -620,7 +623,7 @@ public class OpcuaProtocolLogic extends Plc4xProtocolBase<OpcuaAPU> implements H
int count = 0;
for ( String field : fieldNames ) {
PlcValue value = null;
- if (results[count].getStatusCode() == null) {
+ if (results[count].getValueSpecified()) {
Variant variant = results[count].getValue();
LOGGER.info("Response of type {}", variant.getClass().toString());
if (variant instanceof VariantBoolean) {
@@ -1059,7 +1062,7 @@ public class OpcuaProtocolLogic extends Plc4xProtocolBase<OpcuaAPU> implements H
} else if (field.getIdentifierType() == OpcuaIdentifierType.NUMBER_IDENTIFIER) {
nodeId = new NodeIdNumeric(NodeIdType.nodeIdTypeNumeric, new NumericNodeId(field.getNamespace(),Long.valueOf(field.getIdentifier())));
} else if (field.getIdentifierType() == OpcuaIdentifierType.GUID_IDENTIFIER) {
- nodeId = new NodeIdGuid(NodeIdType.nodeIdTypeGuid, new GuidNodeId(field.getNamespace(), field.getIdentifier()));
+ nodeId = new NodeIdGuid(NodeIdType.nodeIdTypeGuid, new GuidNodeId(field.getNamespace(), toGuidValue(field.getIdentifier())));
} else if (field.getIdentifierType() == OpcuaIdentifierType.STRING_IDENTIFIER) {
nodeId = new NodeIdString(NodeIdType.nodeIdTypeString, new StringNodeId(field.getNamespace(), new PascalString(field.getIdentifier().length(), field.getIdentifier())));
}
@@ -1183,11 +1186,71 @@ public class OpcuaProtocolLogic extends Plc4xProtocolBase<OpcuaAPU> implements H
return (dateTime - epochOffset) / 10000;
}
- public byte[] encrypt(String data, byte[] publicKey) {
+ /**
+ * Creates an IdentityToken to authenticate with a server.
+ * @param securityPolicy
+ * @return returns an ExtensionObject with an IdentityToken.
+ */
+ private ExtensionObject getIdentityToken(String securityPolicy) {
+ ExpandedNodeId extExpandedNodeId = null;
+ ExtensionObject userIdentityToken = null;
+ switch (securityPolicy) {
+ case "none":
+ //If we aren't using authentication tell the server we would like to login anonymously
+ PascalString anonymousIdentityToken = this.policyId;
+
+ WriteBuffer buffer = new WriteBuffer(anonymousIdentityToken.getLengthInBytes(), true);
+ try{
+ PascalStringIO.staticSerialize(buffer, anonymousIdentityToken);
+ } catch (ParseException e) {
+ LOGGER.error("Failed to serialize the user identity token - {}", anonymousIdentityToken.getStringValue());
+ }
+ extExpandedNodeId = new ExpandedNodeIdFourByte(false,
+ false,
+ null,
+ null,
+ new FourByteNodeId((short) 0, OpcuaNodeIdServices.AnonymousIdentityToken_Encoding_DefaultBinary.getValue()));
+ return new ExtensionObject(extExpandedNodeId, (short) 1, buffer.getData().length, buffer.getData());
+ case "username":
+ //Encrypt the password using the server nonce and server public key
+ byte[] passwordBytes = this.password.getBytes();
+ ByteBuffer encodeableBuffer = ByteBuffer.allocate(4 + passwordBytes.length + this.senderNonce.length);
+ encodeableBuffer.order(ByteOrder.LITTLE_ENDIAN);
+ encodeableBuffer.putInt(passwordBytes.length + this.senderNonce.length);
+ encodeableBuffer.put(passwordBytes);
+ encodeableBuffer.put(this.senderNonce);
+ byte[] encodeablePassword = new byte[4 + passwordBytes.length + this.senderNonce.length];
+ encodeableBuffer.position(0);
+ encodeableBuffer.get(encodeablePassword);
+
+ byte[] encryptedPassword = encrypt(encodeablePassword, senderCertificate);
+ UserNameIdentityToken userNameIdentityToken = new UserNameIdentityToken(
+ new PascalString("username".length(), "username"),
+ new PascalString(this.username.length(), this.username),
+ new PascalByteString(encryptedPassword.length, encryptedPassword),
+ new PascalString(PASSWORD_ENCRYPTION_ALGORITHM.length(), PASSWORD_ENCRYPTION_ALGORITHM)
+ );
+ WriteBuffer bufferUserName = new WriteBuffer(userNameIdentityToken.getLengthInBytes(), true);
+ try{
+ UserNameIdentityTokenIO.staticSerialize(bufferUserName, userNameIdentityToken);
+ } catch (ParseException e) {
+ LOGGER.error("Failed to serialize the user identity token - {}", userNameIdentityToken);
+ }
+ extExpandedNodeId = new ExpandedNodeIdFourByte(false,
+ false,
+ null,
+ null,
+ new FourByteNodeId((short) 0, OpcuaNodeIdServices.UserNameIdentityToken_Encoding_DefaultBinary.getValue()));
+ return new ExtensionObject(extExpandedNodeId, (short) 1, bufferUserName.getData().length, bufferUserName.getData());
+ }
+ return null;
+ }
+
+ public byte[] encrypt(byte[] data, byte[] publicKey) {
try {
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-1AndMGF1Padding");
cipher.init(Cipher.ENCRYPT_MODE, getCertificateX509().getPublicKey());
- return cipher.doFinal(data.getBytes());
+ return cipher.doFinal(data);
} catch (Exception e) {
LOGGER.error("Unable to encrypt Data");
return null;
@@ -1203,4 +1266,12 @@ public class OpcuaProtocolLogic extends Plc4xProtocolBase<OpcuaAPU> implements H
return null;
}
}
+
+ private GuidValue toGuidValue(String identifier) {
+ LOGGER.error("Querying Guid nodes is not supported");
+ byte[] data4 = new byte[] {0,0};
+ byte[] data5 = new byte[] {0,0,0,0,0,0};
+ return new GuidValue(0L,0,0,data4, data5);
+
+ }
}
diff --git a/protocols/opcua/src/main/xslt/opc-types.xsl b/protocols/opcua/src/main/xslt/opc-types.xsl
index f34db8f..ee44a6d 100644
--- a/protocols/opcua/src/main/xslt/opc-types.xsl
+++ b/protocols/opcua/src/main/xslt/opc-types.xsl
@@ -629,7 +629,7 @@
<xsl:when test="$datatype = 'opc:Double'">float 11.52</xsl:when>
<xsl:when test="$datatype = 'opc:Char'">string '1'</xsl:when>
<xsl:when test="$datatype = 'opc:CharArray'">PascalString</xsl:when>
- <xsl:when test="$datatype = 'opc:Guid'">string '-1'</xsl:when>
+ <xsl:when test="$datatype = 'opc:Guid'">GuidValue</xsl:when>
<xsl:when test="$datatype = 'opc:ByteString'">PascalByteString</xsl:when>
<xsl:when test="$datatype = 'opc:DateTime'">int 64</xsl:when>
<xsl:when test="$datatype = 'opc:String'">PascalString</xsl:when>