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

chrisdutz pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/plc4x.git

commit 8cbd2740757cfb52af783139b81a2cd8fb1aa16e
Author: Christofer Dutz <[email protected]>
AuthorDate: Mon Jul 13 11:43:07 2026 +0200

    fix: Addressed two issues in the OPC-UA driver
    - Discovery + encrypted-policy hang
    - divide-by-zero on missing server cert issue
---
 .../java/org/apache/plc4x/java/opcua/OpcuaConnection.java  | 13 ++++++++-----
 .../org/apache/plc4x/java/opcua/context/SecureChannel.java | 14 ++++++++++++--
 .../plc4x/java/opcua/protocol/chunk/ChunkFactory.java      |  9 +++++++++
 .../org/apache/plc4x/java/opcua/OpcuaPlcDriverTest.java    |  2 --
 4 files changed, 29 insertions(+), 9 deletions(-)

diff --git 
a/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/OpcuaConnection.java
 
b/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/OpcuaConnection.java
index fb31b356bd..ddd84e6d3a 100644
--- 
a/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/OpcuaConnection.java
+++ 
b/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/OpcuaConnection.java
@@ -164,10 +164,7 @@ public class OpcuaConnection extends 
ConnectionBase<OpcuaConfiguration> implemen
         try {
             // Discovery only carries information needed for the encrypted 
modes
             // (it fetches the server certificate). For SecurityPolicy.NONE the
-            // GetEndpoints call adds nothing and — running on the same TCP
-            // socket as the real handshake — a second HELLO after the 
discovery
-            // CloseSecureChannel makes most servers either reset the 
connection
-            // or hang waiting for nothing.
+            // GetEndpoints call adds nothing, so we skip it.
             if (configuration.isDiscovery()
                 && configuration.getSecurityPolicy() != null
                 && configuration.getSecurityPolicy() != 
org.apache.plc4x.java.opcua.security.SecurityPolicy.NONE) {
@@ -176,8 +173,14 @@ public class OpcuaConnection extends 
ConnectionBase<OpcuaConfiguration> implemen
                     .get(configuration.getNegotiationTimeout(), 
TimeUnit.MILLISECONDS);
                 configuration.setServerCertificate(
                     
getX509Certificate(endpoint.getServerCertificate().getStringValue()));
+                // onDiscover() already performed Hello + OpenSecureChannel on 
this TCP
+                // connection, so the secure channel is open. Reuse it and 
only establish the
+                // session — a second Hello on the same connection would stall 
(Hello is a
+                // once-per-connection message).
+                
channel.onConnectSession().get(configuration.getNegotiationTimeout(), 
TimeUnit.MILLISECONDS);
+            } else {
+                channel.onConnect().get(configuration.getNegotiationTimeout(), 
TimeUnit.MILLISECONDS);
             }
-            channel.onConnect().get(configuration.getNegotiationTimeout(), 
TimeUnit.MILLISECONDS);
             connected = true;
             LOGGER.info("Established connection to server");
         } catch (Exception e) {
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 b6a7ad1c27..37ba25af84 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
@@ -131,8 +131,18 @@ public class SecureChannel {
         LOGGER.debug("Opcua Driver running in ACTIVE mode.");
         return conversation.requestHello()
             .thenCompose(r -> 
onConnectOpenSecureChannel(SecurityTokenRequestType.securityTokenRequestTypeIssue,
 0, 0))
-            .thenCompose(r -> onConnectCreateSessionRequest())
-            .thenCompose(r -> onConnectActivateSessionRequest(r))
+            .thenCompose(r -> onConnectSession());
+    }
+
+    /**
+     * Establishes the session (CreateSession + ActivateSession) on an 
already-open secure
+     * channel. Used after {@link #onDiscover()}, which has already performed 
the
+     * Hello/OpenSecureChannel exchange: sending a second Hello on the same 
TCP connection
+     * would stall, since Hello is a once-per-connection message.
+     */
+    public CompletableFuture<ActivateSessionResponse> onConnectSession() {
+        return onConnectCreateSessionRequest()
+            .thenCompose(this::onConnectActivateSessionRequest)
             .thenApply(response -> {
                 renewToken();
                 return response;
diff --git 
a/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/protocol/chunk/ChunkFactory.java
 
b/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/protocol/chunk/ChunkFactory.java
index 39481441ce..4ffc2a2ef2 100644
--- 
a/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/protocol/chunk/ChunkFactory.java
+++ 
b/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/protocol/chunk/ChunkFactory.java
@@ -23,6 +23,7 @@ import java.security.PublicKey;
 import java.security.cert.X509Certificate;
 import java.security.interfaces.RSAPublicKey;
 import org.apache.commons.codec.digest.DigestUtils;
+import org.apache.plc4x.java.api.exceptions.PlcRuntimeException;
 import org.apache.plc4x.java.opcua.readwrite.OpcuaProtocolLimits;
 import org.apache.plc4x.java.opcua.security.SecurityPolicy;
 
@@ -55,6 +56,14 @@ public class ChunkFactory {
 
         int localAsymmetricKeyLength = asymmetric ? keySize(localCertificate) 
: 0;
         int remoteAsymmetricKeyLength = asymmetric ? 
keySize(remoteCertificate) : 0;
+        // An asymmetric (OpenSecureChannel) chunk is encrypted with the 
server's RSA public
+        // key. Without a valid server certificate its key length is 0, which 
would later divide
+        // by zero when computing the cipher-text block count. Fail fast with 
a clear message.
+        if (asymmetric && remoteAsymmetricKeyLength == 0) {
+            throw new PlcRuntimeException("Cannot open an encrypted OPC UA 
secure channel: no valid server (remote) "
+                + "RSA certificate is available to encrypt the 
OpenSecureChannel request. Provide the server "
+                + "certificate via 'server-certificate-file', or use 
security-policy=NONE.");
+        }
         int localCertificateSize = asymmetric ? 
certificateBytes(localCertificate).length : 0;
         int serverCertificateThumbprint = asymmetric ? 
certificateThumbprint(remoteCertificate).length : 0;
 
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 ffe90b0cba..492451ba09 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
@@ -649,8 +649,6 @@ public class OpcuaPlcDriverTest {
             case Aes128_Sha256_RsaOaep:
             case Aes256_Sha256_RsaPss:
                 String connectionParams = params(
-                    // Trust is pinned via server-certificate-file below, so 
skip discovery.
-                    entry("discovery", "false"),
                     entry("key-store-file", 
CLIENT_KEY_STORE.getAbsoluteFile().toString().replace("\\", "/")), // handle 
windows paths
                     entry("key-store-password", "changeit"),
                     entry("key-store-type", "pkcs12"),

Reply via email to