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"),
