Description
What would you like to happen?
Hello! First of all I'm pretty sure I'm missing something but i'll try.
I created an OPCUA Python server with username or certificates authentication policies. It has been tested with OPCUA Expert and i can access it with both policies methods.
I (and ChatGPT) created a simple standalone example to connect to the server:
package opcua;
import org.apache.plc4x.java.api.PlcConnection;
import org.apache.plc4x.java.api.messages.PlcReadRequest;
import org.apache.plc4x.java.api.messages.PlcReadResponse;
import org.apache.plc4x.java.DefaultPlcDriverManager;
import java.util.concurrent.CompletableFuture;
public class OpcUaClientWithCert {
public static void main(String[] args) {
// URL del server OPC UA, con certificato client e chiave privata inclusi
String serverUrl = "opcua:tcp://127.0.0.1:4840";
String discovery = "true";
String securityPolicy = "Basic256Sha256"; // Percorso del certificato del client
String clientKeyStore = ".../client_keystore.p12";
String clientKeySotrePass = "clientKeystorePassword"; // Password della chiave
// Modificare la stringa di connessione per includere certificato e chiave privata
String connectionString = String.format(
"%s?discovery=%s&security-policy=%s&key-store-file=%s&key-store-password=%s",
serverUrl, discovery, securityPolicy, clientKeyStore, clientKeySotrePass
);
String nodeId = "ns=2;i=2"; // Sostituire con il nodo che si desidera leggere
// Connettersi al server OPC UA con certificati
DefaultPlcDriverManager driverManager = new DefaultPlcDriverManager(); // Crea un'istanza di DefaultPlcDriverManager
try (PlcConnection connection = driverManager.getConnection(connectionString)) {
if (connection != null && connection.isConnected()) {
System.out.println("Connessione stabilita con il server OPC UA usando certificati.");
// Creare una richiesta di lettura
PlcReadRequest.Builder builder = connection.readRequestBuilder();
builder.addTagAddress("myNode", nodeId); // Aggiungere il nodo da leggere
PlcReadRequest readRequest = builder.build();
// Eseguire la lettura in modo asincrono
CompletableFuture<? extends PlcReadResponse> future = readRequest.execute();
PlcReadResponse response = future.get();
// Stampare il risultato
System.out.println("Valore letto dal nodo: " + response.getObject("myNode"));
} else {
System.out.println("Connessione fallita.");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
And this is the pom.xml file:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>test</groupId>
<artifactId>opcua</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>test</name>
<description>test</description>
<dependencies>
<dependency>
<groupId>org.apache.plc4x</groupId>
<artifactId>plc4j-api</artifactId>
<version>0.12.0</version>
</dependency>
<dependency>
<groupId>org.apache.plc4x</groupId>
<artifactId>plc4j-driver-opcua</artifactId>
<version>0.12.0</version>
</dependency>
<dependency>
<groupId>org.apache.plc4x</groupId>
<artifactId>plc4j-transport-tcp</artifactId>
<version>0.12.0</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.36</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.36</version>
</dependency>
</dependencies>
</project>
When it runs, I get the "Unable to find endpoint.." exception in this part of SecureChannel.java
private void selectEndpoint(CreateSessionResponse sessionResponse) throws PlcRuntimeException {
// Get a list of the endpoints which match ours.
Stream<EndpointDescription> filteredEndpoints = sessionResponse.getServerEndpoints().stream()
.map(e -> (EndpointDescription) e)
.filter(this::isEndpoint);
//Determine if the requested security policy is included in the endpoint
filteredEndpoints.forEach(endpoint -> hasIdentity(
endpoint.getUserIdentityTokens().stream()
.map(p -> (UserTokenPolicy) p)
.toArray(UserTokenPolicy[]::new)
));
if (this.policyId == null) {
throw new PlcRuntimeException("Unable to find endpoint - " + this.endpoints.get(0));
}
if (this.tokenType == null) {
throw new PlcRuntimeException("Unable to find Security Policy for endpoint - " + this.endpoints.get(0));
}
}
And noticed this is because the policyId is set to null but because apparently the hasIdentity function has no option for policy other than anonymous and username:
private void hasIdentity(UserTokenPolicy[] policies) {
for (UserTokenPolicy identityToken : policies) {
if ((identityToken.getTokenType() == UserTokenType.userTokenTypeAnonymous) && (this.username == null)) {
policyId = identityToken.getPolicyId();
tokenType = identityToken.getTokenType();
} else if ((identityToken.getTokenType() == UserTokenType.userTokenTypeUserName) && (this.username != null)) {
policyId = identityToken.getPolicyId();
tokenType = identityToken.getTokenType();
}
}
}
The policies that my server returns correspond to the ones i set with the python script:
What am I missing? Shouldn't the library allow this kind of access with just the cetificate? I expected other "if" for certificate policies. Did i end up in other functions that should not be used for this policies? In this case, is there something wrong with the main function?
Please note that the example works if i modify the connection string by just adding the username & password, correctly reading the node value:
String serverUrl = "opcua:tcp://127.0.0.1:4840";
String discovery = "true";
String securityPolicy = "Basic256Sha256"; // Percorso del certificato del client
String clientKeyStore = "..../client_keystore.p12";
String clientKeySotrePass = "clientKeystorePassword"; // Password della chiave
String userName = "user1";
String password = "password1";
// Modificare la stringa di connessione per includere certificato e chiave privata
String connectionString = String.format(
"%s?discovery=%s&security-policy=%s&key-store-file=%s&key-store-password=%s&username=%s&password=%s",
serverUrl, discovery, securityPolicy, clientKeyStore, clientKeySotrePass, userName, password
);
But it does not surprise me because it is using the username policy in this case (even if Basic256Sha256 is still specified)
Programming Languages
- plc4j
- plc4go
- plc4c
- plc4net
Protocols
- AB-Ethernet
- ADS /AMS
- BACnet/IP
- CANopen
- DeltaV
- DF1
- EtherNet/IP
- Firmata
- KNXnet/IP
- Modbus
- OPC-UA
- S7