From 0b13451c7ca8646feff9ea9361acfb0723b2c5ee Mon Sep 17 00:00:00 2001 From: SimoneTinella Date: Tue, 15 Oct 2019 21:55:57 +0200 Subject: [PATCH 01/26] Fixed issue #194 Updated version to 1.4.1.1-SNAPSHOT --- .gitignore | 7 ++-- Changelog.txt | 3 ++ codegen/pom.xml | 2 +- examples/basic/pom.xml | 26 +++++++-------- examples/publisher/pom.xml | 2 +- graveyard/pom.xml | 2 +- pom.xml | 33 ++++++++++--------- .../ua/utils/BouncyCastleUtils.java | 14 ++++---- 8 files changed, 48 insertions(+), 41 deletions(-) diff --git a/.gitignore b/.gitignore index c294db80..01baa213 100644 --- a/.gitignore +++ b/.gitignore @@ -3,10 +3,11 @@ tmp/ javadoc/ bin/ /test-output -target +target +/.idea -*.der -*.pem +*.der +*.pem .settings .classpath .project diff --git a/Changelog.txt b/Changelog.txt index 3abadc0d..b194ed72 100644 --- a/Changelog.txt +++ b/Changelog.txt @@ -1,3 +1,6 @@ +1.4.1.1 +Fixed (#194): SpongyCastle provider is now automatically picked in BouncyCastleUtils class + 1.4.1 New (#185): Support for ReverseHello (see the ReverseHelloClientServerExample) New: Better configuration of initial timeouts in opc.tcp connections (see OpcTcpSettings) diff --git a/codegen/pom.xml b/codegen/pom.xml index b512d7a9..4c0c1bc7 100644 --- a/codegen/pom.xml +++ b/codegen/pom.xml @@ -1,7 +1,7 @@ 4.0.0 opc-ua-stack-codegen - 1.4.1-SNAPSHOT + 1.4.1.1-SNAPSHOT org.opcfoundation.ua diff --git a/examples/basic/pom.xml b/examples/basic/pom.xml index 5ab6992f..58ebc3e4 100644 --- a/examples/basic/pom.xml +++ b/examples/basic/pom.xml @@ -2,7 +2,7 @@ 4.0.0 org.opcfoundation.ua opc-ua-stack-examples - 1.4.1-SNAPSHOT + 1.4.1.1-SNAPSHOT examples UTF-8 @@ -64,12 +64,12 @@ - - make-assembly - package - - single - + + make-assembly + package + + single + @@ -120,12 +120,12 @@ - - app-assembly - package - - assemble - + + app-assembly + package + + assemble + diff --git a/examples/publisher/pom.xml b/examples/publisher/pom.xml index d5f263cf..b9dd4d0c 100644 --- a/examples/publisher/pom.xml +++ b/examples/publisher/pom.xml @@ -2,7 +2,7 @@ 4.0.0 org.opcfoundation.ua azure-client-publisher-sample -1.4.1-SNAPSHOT +1.4.1.1-SNAPSHOT Azure IotHub Client Publisher Sample UTF-8 diff --git a/graveyard/pom.xml b/graveyard/pom.xml index 79967561..b239be2c 100644 --- a/graveyard/pom.xml +++ b/graveyard/pom.xml @@ -4,7 +4,7 @@ 4.0.0 org.opcfoundation.ua opc-ua-stack-extras - 1.4.1-SNAPSHOT + 1.4.1.1-SNAPSHOT extras UTF-8 diff --git a/pom.xml b/pom.xml index 42d81a92..a63212e4 100755 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 org.opcfoundation.ua opc-ua-stack - 1.4.1-SNAPSHOT + 1.4.1.1-SNAPSHOT OPC Foundation Unified Architecture Java Stack UTF-8 @@ -91,7 +91,10 @@ --> - param,return,throws + param,return,throws + ${javadoc.arg} false @@ -111,27 +114,27 @@ jacoco-maven-plugin 0.7.6.201602180812 - - prepare-unit-tests - - prepare-agent + + prepare-unit-tests + + prepare-agent org.opcfoundation.ua.core.Identifiers - + - - prepare-integration-tests - - prepare-agent-integration + + prepare-integration-tests + + prepare-agent-integration org.opcfoundation.ua.core.Identifiers - + @@ -270,11 +273,11 @@ - + jdk6 - + @@ -311,7 +314,7 @@ - + \ No newline at end of file diff --git a/src/main/java/org/opcfoundation/ua/utils/BouncyCastleUtils.java b/src/main/java/org/opcfoundation/ua/utils/BouncyCastleUtils.java index 6355fe82..f500ea19 100644 --- a/src/main/java/org/opcfoundation/ua/utils/BouncyCastleUtils.java +++ b/src/main/java/org/opcfoundation/ua/utils/BouncyCastleUtils.java @@ -84,8 +84,8 @@ public class BouncyCastleUtils { // startDate, expiryDate, dn, // keyPair.getPublic()); // ContentSigner signer = new JcaContentSignerBuilder("SHA1withRSA") -// .setProvider("BC").build(keyPair.getPrivate()); -// return new JcaX509CertificateConverter().setProvider("BC") +// .setProvider(CryptoUtil.getSecurityProviderName()).build(keyPair.getPrivate()); +// return new JcaX509CertificateConverter().setProvider(CryptoUtil.getSecurityProviderName()) // .getCertificate(certBldr.build(signer)); // } @@ -149,11 +149,11 @@ expiryDate, new X500Principal(commonName), ContentSigner signer; try { signer = new JcaContentSignerBuilder(CertificateUtils.getCertificateSignatureAlgorithm()) - .setProvider("BC").build(privateKey); + .setProvider(CryptoUtil.getSecurityProviderName()).build(privateKey); } catch (OperatorCreationException e) { throw new GeneralSecurityException("Failed to sign the certificate", e); } - return new JcaX509CertificateConverter().setProvider("BC") + return new JcaX509CertificateConverter().setProvider(CryptoUtil.getSecurityProviderName()) .getCertificate(certBldr.build(signer)); } @@ -299,9 +299,9 @@ public static X509Certificate generateCertificate(String domainName, PublicKey p //***** generate certificate ***********/ try { ContentSigner signer = new JcaContentSignerBuilder( - CertificateUtils.getCertificateSignatureAlgorithm()).setProvider("BC") + CertificateUtils.getCertificateSignatureAlgorithm()).setProvider(CryptoUtil.getSecurityProviderName()) .build(signerKey); - return new JcaX509CertificateConverter().setProvider("BC") + return new JcaX509CertificateConverter().setProvider(CryptoUtil.getSecurityProviderName()) .getCertificate(certBldr.build(signer)); } catch (OperatorCreationException e) { throw new GeneralSecurityException(e); @@ -326,7 +326,7 @@ public static void writeToPem(Object key, File savePath, String password, String pemWrt.writeObject(key); else { char[] pw = password.toCharArray(); - PEMEncryptor encryptor = new JcePEMEncryptorBuilder(algorithm).setProvider("BC").setSecureRandom(CryptoUtil.getRandom()).build(pw); + PEMEncryptor encryptor = new JcePEMEncryptorBuilder(algorithm).setProvider(CryptoUtil.getSecurityProviderName()).setSecureRandom(CryptoUtil.getRandom()).build(pw); pemWrt.writeObject(key, encryptor); } From 8d6a463d670633d4192ebcdb7ec14e92c7cfb2f0 Mon Sep 17 00:00:00 2001 From: SimoneTinella Date: Sat, 4 Apr 2020 14:37:13 +0200 Subject: [PATCH 02/26] fix pom --- pom.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index 890bf5ba..c6adef94 100755 --- a/pom.xml +++ b/pom.xml @@ -92,10 +92,10 @@ --> param,return,throws - - ${javadoc.arg} + + false From c3be65aa229bde3f1e776714ff1075ba3ea2d218 Mon Sep 17 00:00:00 2001 From: Pradeep Patel Date: Mon, 7 Sep 2020 14:48:47 +0530 Subject: [PATCH 03/26] fix added for issue 192 --- src/main/java/org/opcfoundation/ua/builtintypes/NodeId.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/opcfoundation/ua/builtintypes/NodeId.java b/src/main/java/org/opcfoundation/ua/builtintypes/NodeId.java index d9501734..e3c38682 100644 --- a/src/main/java/org/opcfoundation/ua/builtintypes/NodeId.java +++ b/src/main/java/org/opcfoundation/ua/builtintypes/NodeId.java @@ -357,7 +357,7 @@ public static NodeId parseNodeId(String nodeIdRef) m = INT_INT.matcher(nodeIdRef); if (m.matches()) { Integer nsi = Integer.valueOf( m.group(1) ); - Integer obj = Integer.valueOf( m.group(2) ); + UnsignedInteger obj = UnsignedInteger.valueOf( m.group(2) ); return new NodeId(nsi, obj); } From e820aeb56493493d781bf062f201016e09bb32fd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Oct 2020 19:48:44 +0000 Subject: [PATCH 04/26] Bump junit from 4.12 to 4.13.1 Bumps [junit](https://github.com/junit-team/junit4) from 4.12 to 4.13.1. - [Release notes](https://github.com/junit-team/junit4/releases) - [Changelog](https://github.com/junit-team/junit4/blob/main/doc/ReleaseNotes4.12.md) - [Commits](https://github.com/junit-team/junit4/compare/r4.12...r4.13.1) Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) mode change 100755 => 100644 pom.xml diff --git a/pom.xml b/pom.xml old mode 100755 new mode 100644 index 938eaea0..a1ef903d --- a/pom.xml +++ b/pom.xml @@ -253,7 +253,7 @@ junit junit - 4.12 + 4.13.1 test From aea41f8d836a02eddf8dce3aa9f718edac00ea8d Mon Sep 17 00:00:00 2001 From: Pradeip Patel <33113394+pradeipk@users.noreply.github.com> Date: Tue, 13 Oct 2020 14:59:13 +0530 Subject: [PATCH 05/26] Fix Against Issue#192, Changes incorporated as suggested by the Jouni Aro. (Issue in parseNodeId function in NodeId) --- src/main/java/org/opcfoundation/ua/builtintypes/NodeId.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/opcfoundation/ua/builtintypes/NodeId.java b/src/main/java/org/opcfoundation/ua/builtintypes/NodeId.java index e3c38682..7f882c11 100644 --- a/src/main/java/org/opcfoundation/ua/builtintypes/NodeId.java +++ b/src/main/java/org/opcfoundation/ua/builtintypes/NodeId.java @@ -337,7 +337,7 @@ public static NodeId parseNodeId(String nodeIdRef) m = NONE_INT.matcher(nodeIdRef); if (m.matches()) { - Integer obj = Integer.valueOf( m.group(1) ); + UnsignedInteger obj = UnsignedInteger.valueOf( m.group(1) ); return new NodeId(0, obj); } From 81fb7e17f5f8550cfc905912ed6dd030ae10a698 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 3 Jun 2021 23:54:25 +0000 Subject: [PATCH 06/26] Bump httpclient from 4.5.2 to 4.5.13 in /examples/publisher Bumps httpclient from 4.5.2 to 4.5.13. --- updated-dependencies: - dependency-name: org.apache.httpcomponents:httpclient dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- examples/publisher/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/publisher/pom.xml b/examples/publisher/pom.xml index bb898eda..6bcc305e 100644 --- a/examples/publisher/pom.xml +++ b/examples/publisher/pom.xml @@ -22,7 +22,7 @@ org.apache.httpcomponents httpclient - 4.5.2 + 4.5.13 org.slf4j From 5b3f915702f20bc2d180bbec9d8d8cc30efe39ac Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 3 Jun 2021 23:54:43 +0000 Subject: [PATCH 07/26] Bump httpclient from 4.5.6 to 4.5.13 Bumps httpclient from 4.5.6 to 4.5.13. --- updated-dependencies: - dependency-name: org.apache.httpcomponents:httpclient dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) mode change 100755 => 100644 pom.xml diff --git a/pom.xml b/pom.xml old mode 100755 new mode 100644 index 938eaea0..6564e0c1 --- a/pom.xml +++ b/pom.xml @@ -236,7 +236,7 @@ org.apache.httpcomponents httpclient - 4.5.6 + 4.5.13 true From 5bc97d816e21a8ab0a6d03f4510087a0c8866b40 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 3 Jun 2021 23:54:57 +0000 Subject: [PATCH 08/26] Bump httpclient from 4.5.2 to 4.5.13 in /examples/basic Bumps httpclient from 4.5.2 to 4.5.13. --- updated-dependencies: - dependency-name: org.apache.httpcomponents:httpclient dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- examples/basic/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/basic/pom.xml b/examples/basic/pom.xml index 127318ee..63b4aaf1 100644 --- a/examples/basic/pom.xml +++ b/examples/basic/pom.xml @@ -17,7 +17,7 @@ org.apache.httpcomponents httpclient - 4.5.2 + 4.5.13 log4j From b5749b428fb99a950c618a0aa79f02cfbcc3b751 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 4 Jun 2021 01:40:30 +0000 Subject: [PATCH 09/26] Bump httpclient from 4.5.2 to 4.5.13 in /graveyard Bumps httpclient from 4.5.2 to 4.5.13. --- updated-dependencies: - dependency-name: org.apache.httpcomponents:httpclient dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- graveyard/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graveyard/pom.xml b/graveyard/pom.xml index 0277f61d..f7417bb1 100644 --- a/graveyard/pom.xml +++ b/graveyard/pom.xml @@ -19,7 +19,7 @@ org.apache.httpcomponents httpclient - 4.5.2 + 4.5.13 log4j From 4a389a56cf429f4f461e7d01cd679307875a109d Mon Sep 17 00:00:00 2001 From: Jouni Aro Date: Fri, 8 Oct 2021 11:20:28 +0300 Subject: [PATCH 10/26] Fixed: Updated dependencies to log4j and guava --- codegen/pom.xml | 4 ++-- examples/basic/pom.xml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/codegen/pom.xml b/codegen/pom.xml index 4c0c1bc7..63dc1f98 100644 --- a/codegen/pom.xml +++ b/codegen/pom.xml @@ -33,7 +33,7 @@ com.google.guava guava - 20.0 + 31.0.1-jre - + \ No newline at end of file diff --git a/examples/basic/pom.xml b/examples/basic/pom.xml index d08ed0ce..f56b1a1b 100644 --- a/examples/basic/pom.xml +++ b/examples/basic/pom.xml @@ -22,7 +22,7 @@ log4j log4j - 1.2.12 + 1.2.17 runtime From 963cf667f3fe1ef050e0ca7e3bb4d9ab8b5fe943 Mon Sep 17 00:00:00 2001 From: Jouni Aro Date: Fri, 17 Dec 2021 12:19:25 +0200 Subject: [PATCH 11/26] GH#221 Changed from Log4J to LogBack in examples Removed all references to Log4J --- examples/basic/README.md | 18 +++++++++---- examples/basic/pom.xml | 20 +++++++------- .../basic/src/main/resources/log4j.properties | 22 --------------- examples/basic/src/main/resources/logback.xml | 16 +++++++++++ .../ua/examples/debug.properties | 20 -------------- examples/publisher/pom.xml | 20 +++----------- graveyard/pom.xml | 12 --------- pom.xml | 4 +-- .../ua/application/SessionChannel.java | 1 - .../ua/transport/https/HttpsClient.java | 5 ---- .../tcp/impl/ChunkAsymmDecryptVerifier.java | 1 - .../tcp/impl/ChunkAsymmEncryptSigner.java | 1 - .../tcp/impl/ChunkSymmDecryptVerifier.java | 1 - .../tcp/impl/ChunkSymmEncryptSigner.java | 1 - .../ua/transport/tcp/io/SecureChannelTcp.java | 1 - .../ua/transport/tcp/io/TcpConnection.java | 2 +- .../opcfoundation/ua/utils/CryptoUtil.java | 2 +- .../opcfoundation/ua/utils/StackUtils.java | 2 +- src/test/resources/log4j.properties | 27 ------------------- 19 files changed, 48 insertions(+), 128 deletions(-) delete mode 100644 examples/basic/src/main/resources/log4j.properties create mode 100644 examples/basic/src/main/resources/logback.xml delete mode 100644 examples/basic/src/main/resources/org/opcfoundation/ua/examples/debug.properties delete mode 100644 src/test/resources/log4j.properties diff --git a/examples/basic/README.md b/examples/basic/README.md index 30545b05..0fa6669b 100644 --- a/examples/basic/README.md +++ b/examples/basic/README.md @@ -10,11 +10,11 @@ List of samples: * NanoServer is a an example of a minimal working UA server. This server is intended to conform to Nano Embedded Device Server Profile. ## Using + Import this Maven project to your IDE. Assuming your IDE can match the main stack project and this example one, you can start the samples within the IDE directly (works on eclipse). Assuming not or if you want to build the samples using maven, you need to first build the Stack and install/deploy it to your local/internal repository. Alternatively to use from command line: ```mvn clean install``` the main project and then ```mvn clean package``` this project. Bat/sh scripts used for starting the samples are generated to 'target/assemblies/bin'. - ## Certificates The sample applications create several certificates: @@ -30,20 +30,20 @@ See the org.opcfoundation.ua.examples.certs.Examples.java for details on how the Also see the section "Certificate validation" on how to define your own validators. - ## Certificate key size -The default key size for new certificates is 1024 bits. You may extend this to 2048 or 4096 with CertificateUtils.setKeySize() before creating the certificates. +The default key size for new certificates is 2048 bits. You may change this to 1024 or 4096 with CertificateUtils.setKeySize() before creating the certificates, if necessary. Note that the different security policies have limitations, regarding to the certificate keys size: -* Basic128Rsa16 and Basic256 require a certificate of 1024 or 2048 bits -* Basic256Sha256 requires a certificate of 2048 or 4096 bits +* Basic128Rsa16 and Basic256 require a certificate of 1024 or 2048 bits (NOTE THAT THESE ARE DEPRECATED SINCE OPC UA 1.04) +* Basic256Sha256, Aes128Sha256RsaOaep & Aes256Sha256RsaPss require a certificate of 2048 or 4096 bits In order to enable all security protocols, you must define a key size of 2048 bits or enable two application instance certificates. You can use Application.addApplicationInstanceCertificate() to add new certificates as necessary. The stack will automatically use the correct certificate depending on the security policy used for communications. ## HTTPS certificates and .NET client applications + The client applications built with the OPC Foundation .NET stack require that the server's HTTPS certificate is signed by a trusted CA certificate. Therefore, the samples create a SampleCA certificate, which they use to sign the HTTPS certificate. In order to define a certificate (e.g. the SampleCA) as Trusted Root Certificate, you must install that into the respective Windows certificate store. The following instructions define how to do that: @@ -57,6 +57,7 @@ Or you can just * "Browse..." for the "Trusted Root Certificate Authorities" ## Certificate validation + The application object is initialized to accept all certificates with the following definitions: ``` getOpctcpSettings().setCertificateValidator( CertificateValidator.ALLOW_ALL ); @@ -65,6 +66,13 @@ getHttpsSettings().setCertificateValidator( CertificateValidator.ALLOW_ALL ); You can replace the validators with your own implementation (of CertificateValidator) by assigning them to these properties of your Application instance. +## Logging + +The stack is written against SLF4J, which enables a free selection of backend logging libraries to be used. By default, +the example projects use logback 1.2, which is a native SLF4J implementation and supports Java 6. + +You can find the logging configuration from src/main/resources/logback.xml + ## Common Problems - OPC UA Java Stack When running the sample server (ServerExample1) and sample clients (ClientExample1 and SampleClient) you diff --git a/examples/basic/pom.xml b/examples/basic/pom.xml index f56b1a1b..8e688609 100644 --- a/examples/basic/pom.xml +++ b/examples/basic/pom.xml @@ -19,18 +19,18 @@ httpclient 4.5.13 - - log4j - log4j - 1.2.17 + + ch.qos.logback + logback-core + 1.2.9 runtime - - - org.slf4j - slf4j-log4j12 - 1.7.7 + + + ch.qos.logback + logback-classic + 1.2.9 runtime - + org.apache.httpcomponents httpcore-nio diff --git a/examples/basic/src/main/resources/log4j.properties b/examples/basic/src/main/resources/log4j.properties deleted file mode 100644 index 8203a6e8..00000000 --- a/examples/basic/src/main/resources/log4j.properties +++ /dev/null @@ -1,22 +0,0 @@ -# Normal log properties - Logs Warnings, Errors and Severe -log4j.rootLogger=INFO, stdout - -# Print info messages -log4j.logger.org.opcfoundation.ua=INFO - -# stdout outputs to System.out. -log4j.appender.stdout=org.apache.log4j.ConsoleAppender -# stdout uses PatternLayout. -log4j.appender.stdout.layout=org.apache.log4j.PatternLayout -# The conversion pattern uses format specifiers. You might want to -# change the pattern an watch the output format change. -log4j.appender.stdout.layout.ConversionPattern=%-4r %-5p [%t] %37c %3x - %m%n - -# stderr outputs to System.err. -log4j.appender.stderr=org.apache.log4j.ConsoleAppender -log4j.appender.stderr.target=System.err -# stdout uses PatternLayout. -log4j.appender.stderr.layout=org.apache.log4j.PatternLayout -# The conversion pattern uses format specifiers. You might want to -# change the pattern an watch the output format change. -log4j.appender.stderr.layout.ConversionPattern=%-4r %-5p [%t] %37c %3x - %m%n diff --git a/examples/basic/src/main/resources/logback.xml b/examples/basic/src/main/resources/logback.xml new file mode 100644 index 00000000..c2a691c3 --- /dev/null +++ b/examples/basic/src/main/resources/logback.xml @@ -0,0 +1,16 @@ + + + + + + %-4r %-5p [%t] %37c - %m%n + + + + + + + + \ No newline at end of file diff --git a/examples/basic/src/main/resources/org/opcfoundation/ua/examples/debug.properties b/examples/basic/src/main/resources/org/opcfoundation/ua/examples/debug.properties deleted file mode 100644 index 4f9cfd02..00000000 --- a/examples/basic/src/main/resources/org/opcfoundation/ua/examples/debug.properties +++ /dev/null @@ -1,20 +0,0 @@ -# Debug log properties - logs everything -log4j.logger.org.opcfoundation.ua=TRACE, stderr - -# stdout outputs to System.out. -log4j.appender.stdout=org.apache.log4j.ConsoleAppender -# stdout uses PatternLayout. -log4j.appender.stdout.layout=org.apache.log4j.PatternLayout -# The conversion pattern uses format specifiers. You might want to -# change the pattern an watch the output format change. -log4j.appender.stdout.layout.ConversionPattern=%-4r %-5p [%t] %37c %3x - %m%n - -# stdout outputs to System.out. -log4j.appender.stderr=org.apache.log4j.ConsoleAppender -log4j.appender.stderr.target=System.err -# stdout uses PatternLayout. -log4j.appender.stderr.layout=org.apache.log4j.PatternLayout -# The conversion pattern uses format specifiers. You might want to -# change the pattern an watch the output format change. -log4j.appender.stderr.layout.ConversionPattern=%-4r %-5p [%t] %37c %3x - %m%n - diff --git a/examples/publisher/pom.xml b/examples/publisher/pom.xml index 8a724756..a079f595 100644 --- a/examples/publisher/pom.xml +++ b/examples/publisher/pom.xml @@ -24,12 +24,6 @@ httpclient 4.5.13 - - org.slf4j - slf4j-log4j12 - 1.7.7 - runtime - org.apache.httpcomponents httpcore-nio @@ -38,21 +32,15 @@ org.slf4j slf4j-api - 1.7.7 + 1.7.32 org.slf4j - slf4j-log4j12 - 1.7.7 + slf4j-simple + 1.7.32 test - - log4j - log4j - 1.2.12 - test - - + org.bouncycastle bcpkix-jdk15to18 1.64 diff --git a/graveyard/pom.xml b/graveyard/pom.xml index fb2199ee..2228155d 100644 --- a/graveyard/pom.xml +++ b/graveyard/pom.xml @@ -21,18 +21,6 @@ httpclient 4.5.13 - - log4j - log4j - 1.2.12 - runtime - - - org.slf4j - slf4j-log4j12 - 1.7.7 - runtime - org.apache.httpcomponents httpcore-nio diff --git a/pom.xml b/pom.xml index 1cd1144d..fb2f32ec 100644 --- a/pom.xml +++ b/pom.xml @@ -251,7 +251,7 @@ org.slf4j slf4j-api - 1.7.25 + 1.7.32 junit @@ -268,7 +268,7 @@ org.slf4j slf4j-simple - 1.7.25 + 1.7.32 test diff --git a/src/main/java/org/opcfoundation/ua/application/SessionChannel.java b/src/main/java/org/opcfoundation/ua/application/SessionChannel.java index d94fdb5d..17e0d56b 100644 --- a/src/main/java/org/opcfoundation/ua/application/SessionChannel.java +++ b/src/main/java/org/opcfoundation/ua/application/SessionChannel.java @@ -67,7 +67,6 @@ public class SessionChannel extends ChannelService implements RequestChannel { /** - * Log4J Error logger. * Security settings are logged with DEBUG level. * Unexpected errors are logged with ERROR level. */ diff --git a/src/main/java/org/opcfoundation/ua/transport/https/HttpsClient.java b/src/main/java/org/opcfoundation/ua/transport/https/HttpsClient.java index b2dc41bd..226f3a69 100644 --- a/src/main/java/org/opcfoundation/ua/transport/https/HttpsClient.java +++ b/src/main/java/org/opcfoundation/ua/transport/https/HttpsClient.java @@ -82,11 +82,6 @@ public class HttpsClient implements ITransportChannel { static final ServiceResultException BAD_TIMEOUT = new ServiceResultException( Bad_Timeout ); static final Charset UTF8 = Charset.forName("UTF-8"); - /** - * Log4J Error logger. - * Security settings are logged with DEBUG level. - * Unexpected errors are logged with ERROR level. - */ static final Logger logger = LoggerFactory.getLogger(HttpsClient.class); /** Request Id Counter */ diff --git a/src/main/java/org/opcfoundation/ua/transport/tcp/impl/ChunkAsymmDecryptVerifier.java b/src/main/java/org/opcfoundation/ua/transport/tcp/impl/ChunkAsymmDecryptVerifier.java index d8d26b82..a3a1b8e2 100644 --- a/src/main/java/org/opcfoundation/ua/transport/tcp/impl/ChunkAsymmDecryptVerifier.java +++ b/src/main/java/org/opcfoundation/ua/transport/tcp/impl/ChunkAsymmDecryptVerifier.java @@ -36,7 +36,6 @@ public class ChunkAsymmDecryptVerifier implements Runnable { /** - * Log4J Error logger. * Security failures are logged with INFO level. * Security info are printed with DEBUG level. * Unexpected errors are logged with ERROR level. diff --git a/src/main/java/org/opcfoundation/ua/transport/tcp/impl/ChunkAsymmEncryptSigner.java b/src/main/java/org/opcfoundation/ua/transport/tcp/impl/ChunkAsymmEncryptSigner.java index 0e577191..6163abb5 100644 --- a/src/main/java/org/opcfoundation/ua/transport/tcp/impl/ChunkAsymmEncryptSigner.java +++ b/src/main/java/org/opcfoundation/ua/transport/tcp/impl/ChunkAsymmEncryptSigner.java @@ -37,7 +37,6 @@ public class ChunkAsymmEncryptSigner implements Runnable { /** - * Log4J Error logger. * Security failures are logged with INFO level. * Security settings are logged with DEBUG level. * Unexpected errors are logged with ERROR level. diff --git a/src/main/java/org/opcfoundation/ua/transport/tcp/impl/ChunkSymmDecryptVerifier.java b/src/main/java/org/opcfoundation/ua/transport/tcp/impl/ChunkSymmDecryptVerifier.java index f18ccb6a..24c13333 100644 --- a/src/main/java/org/opcfoundation/ua/transport/tcp/impl/ChunkSymmDecryptVerifier.java +++ b/src/main/java/org/opcfoundation/ua/transport/tcp/impl/ChunkSymmDecryptVerifier.java @@ -32,7 +32,6 @@ public class ChunkSymmDecryptVerifier implements Runnable { /** - * Log4J Error logger. * Security failures are logged with INFO level. * Security settings are logged with DEBUG level. * Unexpected errors are logged with ERROR level. diff --git a/src/main/java/org/opcfoundation/ua/transport/tcp/impl/ChunkSymmEncryptSigner.java b/src/main/java/org/opcfoundation/ua/transport/tcp/impl/ChunkSymmEncryptSigner.java index def06cd5..0ac4c3c1 100644 --- a/src/main/java/org/opcfoundation/ua/transport/tcp/impl/ChunkSymmEncryptSigner.java +++ b/src/main/java/org/opcfoundation/ua/transport/tcp/impl/ChunkSymmEncryptSigner.java @@ -28,7 +28,6 @@ public class ChunkSymmEncryptSigner implements Runnable { /** - * Log4J Error logger. * Security failures are logged with INFO level. * Security settings are logged with DEBUG level. * Unexpected errors are logged with ERROR level. diff --git a/src/main/java/org/opcfoundation/ua/transport/tcp/io/SecureChannelTcp.java b/src/main/java/org/opcfoundation/ua/transport/tcp/io/SecureChannelTcp.java index da699334..aaa4fcbe 100644 --- a/src/main/java/org/opcfoundation/ua/transport/tcp/io/SecureChannelTcp.java +++ b/src/main/java/org/opcfoundation/ua/transport/tcp/io/SecureChannelTcp.java @@ -97,7 +97,6 @@ public class SecureChannelTcp implements IMessageListener, IConnectionListener, ITransportChannel, org.opcfoundation.ua.transport.SecureChannel { /** - * Log4J Error logger. * Security settings are logged with DEBUG level. * Unexpected errors are logged with ERROR level. */ diff --git a/src/main/java/org/opcfoundation/ua/transport/tcp/io/TcpConnection.java b/src/main/java/org/opcfoundation/ua/transport/tcp/io/TcpConnection.java index af917330..03686e99 100644 --- a/src/main/java/org/opcfoundation/ua/transport/tcp/io/TcpConnection.java +++ b/src/main/java/org/opcfoundation/ua/transport/tcp/io/TcpConnection.java @@ -174,7 +174,7 @@ public ByteBuffer[] getPlaintexts() { } /** - * Log4J Error logger. Security settings are logged with DEBUG level. + * Security settings are logged with DEBUG level. * Unexpected errors are logged with ERROR level. */ static Logger logger = LoggerFactory.getLogger(TcpConnection.class); diff --git a/src/main/java/org/opcfoundation/ua/utils/CryptoUtil.java b/src/main/java/org/opcfoundation/ua/utils/CryptoUtil.java index 19e1fddc..0aecade6 100644 --- a/src/main/java/org/opcfoundation/ua/utils/CryptoUtil.java +++ b/src/main/java/org/opcfoundation/ua/utils/CryptoUtil.java @@ -59,7 +59,7 @@ public class CryptoUtil { /** - * Log4J Error logger. Security failures are logged with INFO level. + * Security failures are logged with INFO level. * Security info are printed with DEBUG level. Unexpected errors are logged * with ERROR level. */ diff --git a/src/main/java/org/opcfoundation/ua/utils/StackUtils.java b/src/main/java/org/opcfoundation/ua/utils/StackUtils.java index 1bf005d9..87e5dba4 100644 --- a/src/main/java/org/opcfoundation/ua/utils/StackUtils.java +++ b/src/main/java/org/opcfoundation/ua/utils/StackUtils.java @@ -409,7 +409,7 @@ public static void setBlockingWorkerThreadPoolTimeout( /** * Define the handler that is called, if any of the worker threads encounter an exception that is not handled. *

- * The default handler will just log the exception as an Error to the log4j log. + * The default handler will just log the exception as an ERROR to the logger. *

* Set the handler to provide custom behavior in your application. *

diff --git a/src/test/resources/log4j.properties b/src/test/resources/log4j.properties deleted file mode 100644 index a1d2eeed..00000000 --- a/src/test/resources/log4j.properties +++ /dev/null @@ -1,27 +0,0 @@ -# Normal log properties - Logs Warnings, Errors and Severe -log4j.rootLogger=OFF, stdout - -# Print errors -#log4j.logger.org.opcfoundation.ua=ERROR, stdout - -# Print everything -log4j.logger.org.opcfoundation.ua=OFF -log4j.logger.org.opcfoundation.ua.unittests=OFF -log4j.logger.org.opcfoundation.ua.transport.tcp.impl=OFF - -# stdout outputs to System.out. -log4j.appender.stdout=org.apache.log4j.ConsoleAppender -# stdout uses PatternLayout. -log4j.appender.stdout.layout=org.apache.log4j.PatternLayout -# The conversion pattern uses format specifiers. You might want to -# change the pattern an watch the output format change. -log4j.appender.stdout.layout.ConversionPattern=%-4r %-5p [%t] %37c %3x - %m%n - -# stdout outputs to System.out. -log4j.appender.stderr=org.apache.log4j.ConsoleAppender -log4j.appender.stderr.target=System.err -# stdout uses PatternLayout. -log4j.appender.stderr.layout=org.apache.log4j.PatternLayout -# The conversion pattern uses format specifiers. You might want to -# change the pattern an watch the output format change. -log4j.appender.stderr.layout.ConversionPattern=%-4r %-5p [%t] %37c %3x - %m%n From 6548c6f94166bc18e9f38a28540b6ae85a299575 Mon Sep 17 00:00:00 2001 From: Jouni Aro Date: Fri, 13 May 2022 17:23:18 +0300 Subject: [PATCH 12/26] Wait with timeout to not block IncubationQueue forever --- .../java/org/opcfoundation/ua/utils/IncubationQueue.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/opcfoundation/ua/utils/IncubationQueue.java b/src/main/java/org/opcfoundation/ua/utils/IncubationQueue.java index 60e65374..dce6498e 100644 --- a/src/main/java/org/opcfoundation/ua/utils/IncubationQueue.java +++ b/src/main/java/org/opcfoundation/ua/utils/IncubationQueue.java @@ -206,7 +206,11 @@ public T getNextHatched() public synchronized T getNext() throws InterruptedException { - while(orderList.isEmpty()) wait(); + while(orderList.isEmpty()) { + wait(5000); + if (orderList.isEmpty()) + throw new InterruptedException("Maximum wait time reached, incubation queue still empty"); + } return orderList.getFirst(); } From 50c8df64c357806b4cdcfb26bbd4d0028baaea40 Mon Sep 17 00:00:00 2001 From: Jouni Aro Date: Fri, 13 May 2022 17:23:18 +0300 Subject: [PATCH 13/26] Delay message decoding to avoid waiting --- .../tcp/nio/OpcTcpServerConnection.java | 6 ++- .../tcp/nio/SecureInputMessageBuilder.java | 16 +++++- .../ua/utils/IncubationQueue.java | 24 +++++++-- .../ua/utils/bytebuffer/IncubationBuffer.java | 54 ++++++++++++++----- 4 files changed, 80 insertions(+), 20 deletions(-) diff --git a/src/main/java/org/opcfoundation/ua/transport/tcp/nio/OpcTcpServerConnection.java b/src/main/java/org/opcfoundation/ua/transport/tcp/nio/OpcTcpServerConnection.java index 67712aa4..8ee9d3e4 100644 --- a/src/main/java/org/opcfoundation/ua/transport/tcp/nio/OpcTcpServerConnection.java +++ b/src/main/java/org/opcfoundation/ua/transport/tcp/nio/OpcTcpServerConnection.java @@ -817,7 +817,11 @@ protected void handleSymmChunk(ByteBuffer chunk) throws ServiceResultException { } } logger.debug("handleSymmChunk: {}", secureMessageBuilder); - if (secureMessageBuilder!=null && !secureMessageBuilder.moreChunksRequired()) secureMessageBuilder = null; + if (secureMessageBuilder!=null && !secureMessageBuilder.moreChunksRequired()) { + // Ensures streams eventually close by putting a "poison pill" there + secureMessageBuilder.softClose(); + secureMessageBuilder = null; + } if (secureMessageBuilder==null) { secureMessageBuilder = new SecureInputMessageBuilder(token/*channel*/, messageListener, ctx, encoderCtx, channel.recvSequenceNumber); logger.debug("handleSymmChunk: secureMessageBuilder={}", secureMessageBuilder); diff --git a/src/main/java/org/opcfoundation/ua/transport/tcp/nio/SecureInputMessageBuilder.java b/src/main/java/org/opcfoundation/ua/transport/tcp/nio/SecureInputMessageBuilder.java index 70cf2c28..166f5e4c 100644 --- a/src/main/java/org/opcfoundation/ua/transport/tcp/nio/SecureInputMessageBuilder.java +++ b/src/main/java/org/opcfoundation/ua/transport/tcp/nio/SecureInputMessageBuilder.java @@ -190,6 +190,13 @@ public synchronized void addChunk(final ByteBuffer chunk) throws ServiceResultEx } chunkSink.incubate(chunk); + + // IF it was the final chunk, "close" the buffer so that it knows no more data is to + // come for it. Note that all the data still comes from the sink, this just stops it after that + if (!acceptsChunks) { + chunkSink.close(); + } + Runnable handleChunkRun = new Runnable() { public void run() { if (hasError()) return; @@ -251,7 +258,8 @@ else if (token instanceof SecurityConfiguration) { StackUtils.getNonBlockingWorkExecutor().execute(handleChunkRun); // Start decoding message - if (chunkNumber==0) + // only after the final chunk has been received + if (!acceptsChunks) StackUtils.getBlockingWorkExecutor().execute(messageDecoderRun); } @@ -341,7 +349,11 @@ public void close() { chunkSink.forceClose(); } - /** + public void softClose() { + chunkSink.close(); + } + + /** *

getMessage.

* * @return a {@link org.opcfoundation.ua.encoding.IEncodeable} object. diff --git a/src/main/java/org/opcfoundation/ua/utils/IncubationQueue.java b/src/main/java/org/opcfoundation/ua/utils/IncubationQueue.java index 60e65374..9aaa5738 100644 --- a/src/main/java/org/opcfoundation/ua/utils/IncubationQueue.java +++ b/src/main/java/org/opcfoundation/ua/utils/IncubationQueue.java @@ -39,11 +39,15 @@ * q.removeNextHatchedIfAvailable(); // returns null * q.hatch("c"); * q.removeNextHatched(); // returns "c" - * - * @author Toni Kalajainen (toni.kalajainen@iki.fi) */ public class IncubationQueue { + /** + * Maximum wait time in milliseconds for blocking operations, such as {@link #getNext()} and + * {@link #waitUntilIncubated(Object)}. Default 60000 (ms). + */ + public static int maxWaitTime = 60000; + Map hatchMap; // value=key, if null it is hatched LinkedList orderList = new LinkedList(); @@ -206,7 +210,13 @@ public T getNextHatched() public synchronized T getNext() throws InterruptedException { - while(orderList.isEmpty()) wait(); + long start = System.currentTimeMillis(); + while (orderList.isEmpty()) { + wait(1000); + if ((System.currentTimeMillis() - start) > maxWaitTime && orderList.isEmpty()) { + throw new InterruptedException("Maximum wait time reached, incubation queue still empty"); + } + } return orderList.getFirst(); } @@ -290,7 +300,13 @@ public synchronized boolean isIncubating(T o) public synchronized void waitUntilIncubated(T o) throws InterruptedException { - while (isIncubating(o)) wait(); + long start = System.currentTimeMillis(); + while (isIncubating(o)) { + wait(1000); + if ((System.currentTimeMillis() - start) > maxWaitTime && isIncubating(o)) { + throw new InterruptedException("Maximum wait time reached; didn't incubate in time"); + } + } } } diff --git a/src/main/java/org/opcfoundation/ua/utils/bytebuffer/IncubationBuffer.java b/src/main/java/org/opcfoundation/ua/utils/bytebuffer/IncubationBuffer.java index f30bd5c4..4c926dcd 100644 --- a/src/main/java/org/opcfoundation/ua/utils/bytebuffer/IncubationBuffer.java +++ b/src/main/java/org/opcfoundation/ua/utils/bytebuffer/IncubationBuffer.java @@ -25,8 +25,6 @@ * The data in ByteBuffers in read in the order they are "incubated" * The data becomes available when the ByteBuffers are "hatched" * Input stream blocks until data becomes available. - * - * @author Toni Kalajainen (toni.kalajainen@vtt.fi) */ public class IncubationBuffer extends InputStream { @@ -34,6 +32,8 @@ public class IncubationBuffer extends InputStream { protected final static ByteBuffer CLOSED_MARKER = ByteBuffer.allocate(0); protected IncubationQueue queue = new IncubationQueue(true); protected ByteBuffer cur; + private boolean closed = false; + private boolean forceClosed = false; /** *

Constructor for IncubationBuffer.

@@ -43,14 +43,21 @@ public IncubationBuffer() { } /** - * Submits a byte buffer to the use of input stream - * - * @param buf byte buffer to offer for use + * Submits a byte buffer to the use of input stream, it can only be used once + * {@link #hatch(ByteBuffer)} has been called for the same buffer. */ public void incubate(ByteBuffer buf) { + // Here compared to hatch we can prevent any new buffers, as the stream already contains all the + // data it can (some of which might not yet be hatched though). + if (closed || forceClosed) { + return; + } synchronized(queue) { - queue.incubate(buf); + if (closed || forceClosed) { + return; + } + queue.incubate(buf); } } @@ -61,8 +68,16 @@ public void incubate(ByteBuffer buf) */ public void hatch(ByteBuffer buf) { + // Note that close here must not prevent hatching, since that happens in async manner. Only + // forceClose should prevent it,as that has already removed data. + if (forceClosed) { + return; + } synchronized(queue) { - queue.hatch(buf); + if (forceClosed) { + return; + } + queue.hatch(buf); } } @@ -71,9 +86,16 @@ public void hatch(ByteBuffer buf) */ public void close() { + if (closed) { + return; + } synchronized(queue) { - queue.incubate(CLOSED_MARKER); - queue.hatch(CLOSED_MARKER); + if (closed) { + return; + } + queue.incubate(CLOSED_MARKER); + queue.hatch(CLOSED_MARKER); // will notifyAll + closed = true; } } @@ -82,10 +104,16 @@ public void close() */ public void forceClose() { + if (forceClosed) { + return; + } synchronized(queue) { - queue.clear(); - queue.incubate(CLOSED_MARKER); - queue.hatch(CLOSED_MARKER); + if (forceClosed) { + return; + } + queue.clear(); // will notifyAll + forceClosed = true; + closed = true; } } @@ -96,7 +124,7 @@ public void forceClose() private ByteBuffer getByteBuffer() throws InterruptedIOException { synchronized(queue) { - if (cur==CLOSED_MARKER) return null; + if (cur==CLOSED_MARKER || forceClosed) return null; if (cur!=null && cur.hasRemaining()) return cur; if (cur!=null && !cur.hasRemaining()) cur = null; try { From 69554d1d4d1fcf78c99e21b309f7c8e72fe4ca12 Mon Sep 17 00:00:00 2001 From: Jouni Aro Date: Fri, 24 Feb 2023 12:02:25 +0200 Subject: [PATCH 14/26] New: MaxConnectionCount New: OpcTcpSettings.maxConnections (default 100) New: OpcTcpSettings.maxSecureChannelsPerConnection (default 1) New: HttpsTcpSettings.maxConnections (default 100) --- .../ua/transport/https/HttpsServer.java | 39 ++++++++- .../ua/transport/https/HttpsSettings.java | 13 ++- .../ua/transport/tcp/io/OpcTcpSettings.java | 16 ++++ .../ua/transport/tcp/nio/OpcTcpServer.java | 87 ++++++++++++++++++- .../tcp/nio/OpcTcpServerConnection.java | 78 +++++++++++++++-- .../tcp/nio/SecureInputMessageBuilder.java | 17 ++++ 6 files changed, 241 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/opcfoundation/ua/transport/https/HttpsServer.java b/src/main/java/org/opcfoundation/ua/transport/https/HttpsServer.java index 18fd8a3e..4e52d2cc 100644 --- a/src/main/java/org/opcfoundation/ua/transport/https/HttpsServer.java +++ b/src/main/java/org/opcfoundation/ua/transport/https/HttpsServer.java @@ -133,6 +133,10 @@ public static TrustManager[] makeTrustManager(CertificateValidator...validators) AtomicInteger secureChannelCounter = new AtomicInteger(); /** Server Socket */ AsyncServerSocket socket; + + int maxConnections; + private boolean initialized = false; + /** Endpoint bindings */ EndpointBindingCollection endpointBindings = new EndpointBindingCollection(); /** Connection listeners */ @@ -215,6 +219,25 @@ public void connected(final NHttpServerConnection conn) { connMap.put(conn, httpsConnection); connections.addConnection( httpsConnection ); super.connected(conn); + + + // Check for connection count limits + List conns = new ArrayList(); + connections.getConnections(conns); + + log.trace("Checking maximum number of connections, limit: {}, current: {}", maxConnections, conns.size()); + + // at limit is enough per the 5.5.2 + if (conns.size() >= maxConnections) { + int delta = maxConnections - conns.size() + 1; + log.trace("We are at max or over limit, closing this connection"); + try { + conn.shutdown(); + connections.removeConnection(httpsConnection); // ensure it is removed + } catch (IOException e) { + log.error("Cannot close opc.https connection properly", e); + } + } } @Override @@ -340,6 +363,18 @@ protected void shutdownReactor() { } } + private void init() { + if (!initialized) { + int maxConnections = application.getHttpsSettings().getMaxConnections(); + if (maxConnections <= 0) { + throw new IllegalStateException("Maximum number of connections was not configured; must be greater than 0"); + } + this.maxConnections = maxConnections; + + initialized = true; + } + } + /** *

initReactor.

* @@ -456,7 +491,9 @@ synchronized SocketHandle getOrCreateSocketHandle(SocketAddress socketAddress, S public EndpointHandle bind(SocketAddress socketAddress, EndpointBinding endpointBinding) throws ServiceResultException { if ( endpointBinding == null || socketAddress == null || endpointBinding.endpointServer!=this ) throw new IllegalArgumentException(); - String url = endpointBinding.endpointAddress.getEndpointUrl(); + init(); + + String url = endpointBinding.endpointAddress.getEndpointUrl(); // Start endpoint handler { diff --git a/src/main/java/org/opcfoundation/ua/transport/https/HttpsSettings.java b/src/main/java/org/opcfoundation/ua/transport/https/HttpsSettings.java index be85d964..d54a7b00 100644 --- a/src/main/java/org/opcfoundation/ua/transport/https/HttpsSettings.java +++ b/src/main/java/org/opcfoundation/ua/transport/https/HttpsSettings.java @@ -43,6 +43,9 @@ public class HttpsSettings { X509KeyManager keyManager; /** Trust managers */ TrustManager trustManager; + + int maxConnections = 100; + /** Verifies whether the target hostname matches the names stored inside * the server's X.509 certificate, once the connection has been established. * This verification can provide additional guarantees of authenticity of @@ -280,7 +283,10 @@ public String getPassword() { return password; } - + public int getMaxConnections() { + return maxConnections; + } + public HttpParams getHttpParams() { return httpParams; } @@ -297,6 +303,10 @@ public void setPassword(String password) { this.password = password; } + public void setMaxConnections(int maxConnections) { + this.maxConnections = maxConnections; + } + public void readFrom(HttpsSettings src) { if (src.hostnameVerifier!=null) hostnameVerifier = src.hostnameVerifier; if (src.trustManager!=null) this.trustManager = src.trustManager; @@ -307,6 +317,7 @@ public void readFrom(HttpsSettings src) { } if ( src.httpParams != null ) this.httpParams = src.httpParams; if ( src.httpsSecurityPolicies != null ) this.httpsSecurityPolicies = src.httpsSecurityPolicies; + this.maxConnections = src.maxConnections; } @Override diff --git a/src/main/java/org/opcfoundation/ua/transport/tcp/io/OpcTcpSettings.java b/src/main/java/org/opcfoundation/ua/transport/tcp/io/OpcTcpSettings.java index 298e6d1d..9ecefb17 100644 --- a/src/main/java/org/opcfoundation/ua/transport/tcp/io/OpcTcpSettings.java +++ b/src/main/java/org/opcfoundation/ua/transport/tcp/io/OpcTcpSettings.java @@ -33,6 +33,9 @@ public class OpcTcpSettings implements Cloneable{ int handshakeTimeout = -1; int connectTimeout = -1; int reverseHelloAcceptTimeout = -1; + int maxConnections = 100; + int maxSecureChannelsPerConnection = 1; + public enum Flag { /** * In multithread mode, depending on implementation, channels @@ -201,6 +204,19 @@ public OpcTcpSettings clone() { result.setHandshakeTimeout(handshakeTimeout); result.setReverseHelloAcceptTimeout(reverseHelloAcceptTimeout); return result; + } + public int getMaxConnections() { + return maxConnections; + } + public void setMaxConnections(int maxConnections) { + this.maxConnections = maxConnections; + } + public int getMaxSecureChannelsPerConnection() { + return maxSecureChannelsPerConnection; + } + public void setMaxSecureChannelsPerConnection( + int maxSecureChannelsPerConnection) { + this.maxSecureChannelsPerConnection = maxSecureChannelsPerConnection; } diff --git a/src/main/java/org/opcfoundation/ua/transport/tcp/nio/OpcTcpServer.java b/src/main/java/org/opcfoundation/ua/transport/tcp/nio/OpcTcpServer.java index bf95040c..1ddba1b7 100644 --- a/src/main/java/org/opcfoundation/ua/transport/tcp/nio/OpcTcpServer.java +++ b/src/main/java/org/opcfoundation/ua/transport/tcp/nio/OpcTcpServer.java @@ -72,6 +72,8 @@ public class OpcTcpServer extends AbstractState socketHandles = new HashMap(); @@ -135,9 +137,70 @@ public void onClosed(ServiceResultException closeError) { public void onOpen() { }}); - }}; + // Check for connection count limits + // Per 1.05 Part 4 section 5.5.2, To protect against misbehaving Clients and denial of + // service attacks, the Server shall close the oldest unused SecureChannelthat has no + // Session assigned before reaching the maximum number of supported SecureChannels. + + + List conns = new ArrayList(); + connections.getConnections(conns); + + logger.trace("Checking maximum number of connections, limit: {}, current: {}", maxConnections, conns.size()); + + // at limit is enough per the 5.5.2 + if (conns.size() >= maxConnections) { + int delta = maxConnections - conns.size() + 1; + logger.trace("We are at max or over limit, number of connections to purge if possible: {}", delta); + // TODO ordering based on timestamps of creation + int purged = 0; + for (ServerConnection tmp : conns) { + if (tmp instanceof OpcTcpServerConnection) { + // Every connection should be of this type in this OpcTcpServer) + OpcTcpServerConnection opcTcpTmp = (OpcTcpServerConnection) tmp; + + // We are itself part of the connections as well. Skip ourselves here (that is + // handled at the end of this method). If we remove ourselves here, and would be the + // "final legit" connection (and then we would be at max capacity), we would be + // closed before we can ActivateSession. + if (conn == opcTcpTmp) { + continue; + } + + if (opcTcpTmp.isPurgeEligible()) { + opcTcpTmp.close(); + purged++; + if (purged >= delta) { + break; + } + } + } + } + logger.trace("We are at max or over limit, purged {} old connections", purged); + } + // It is possible that we were not able to purge anything, if we happen to be at max + // connections that all have activated sessions, in this case, close this connection + // instead. + List tmp = new ArrayList(); + connections.getConnections(tmp); + /* + * But! This connection itself is within the connections, if we are exactly at max, that + * is still OK here. But if we are over (i.e. we already had at max and none cannot be + * removed), then remove this connection. + */ + if (tmp.size() > maxConnections) { + logger.trace("We are at over limit, unable to purge enough old connections, closing this connection"); + conn.close(); + } else if (tmp.size() == maxConnections) { + logger.trace("We are exactly at maximum connections (including this connection). " + + "No older connections could be purged. Keeping this connection open."); + } + }}; ConnectionCollection connections = new ConnectionCollection(this); + int maxConnections; + int maxSecureChannelsPerConnection; + /** *

Constructor for OpcTcpServer.

* @@ -167,6 +230,8 @@ public EndpointHandle bind(SocketAddress socketAddress, EndpointBinding endpoint if ( endpointBinding == null || socketAddress == null || endpointBinding.endpointServer!=this ) throw new IllegalArgumentException(); + init(); + String scheme = UriUtil.getTransportProtocol( endpointBinding.endpointAddress.getEndpointUrl() ); if ( !"opc.tcp".equals(scheme) ) throw new ServiceResultException(StatusCodes.Bad_UnexpectedError, "Cannot bind "+scheme+" to opc.tcp server"); SocketHandle socketHandle = getOrCreateSocketHandle(socketAddress); @@ -204,6 +269,8 @@ public void bindReverse(final SocketAddress addressToConnect, if(addressToConnect == null || endpointUrl == null) { throw new IllegalArgumentException(); } + init(); + ReverseSocketHandle socketHandle = new ReverseSocketHandle(addressToConnect); if(socketHandle.socket == null) { try { @@ -518,6 +585,24 @@ public String toString() { } } + private void init() { + if (!initialized) { + int maxConnections = application.getOpctcpSettings().getMaxConnections(); + if (maxConnections <= 0) { + throw new IllegalStateException("Maximum number of connections was not configured; must be greater than 0"); + } + this.maxConnections = maxConnections; + + int maxSecureChannelsPerConnection = application.getOpctcpSettings().getMaxSecureChannelsPerConnection(); + if (maxSecureChannelsPerConnection <= 0) { + throw new IllegalStateException( + "Maximum number of secure channels per connection was not configured; must be greater than 0"); + } + this.maxSecureChannelsPerConnection = maxSecureChannelsPerConnection; + initialized = true; + } + } + List findEndpoints(String forUri) { List result = new ArrayList(); for (SocketHandle sh : socketHandleSnapshot() ) sh.endpointHandleSnapshot(result); diff --git a/src/main/java/org/opcfoundation/ua/transport/tcp/nio/OpcTcpServerConnection.java b/src/main/java/org/opcfoundation/ua/transport/tcp/nio/OpcTcpServerConnection.java index 8ee9d3e4..9172a1d2 100644 --- a/src/main/java/org/opcfoundation/ua/transport/tcp/nio/OpcTcpServerConnection.java +++ b/src/main/java/org/opcfoundation/ua/transport/tcp/nio/OpcTcpServerConnection.java @@ -31,11 +31,13 @@ import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import org.opcfoundation.ua.builtintypes.StatusCode; import org.opcfoundation.ua.builtintypes.UnsignedInteger; import org.opcfoundation.ua.common.ServiceResultException; +import org.opcfoundation.ua.core.ActivateSessionResponse; import org.opcfoundation.ua.core.CloseSecureChannelRequest; import org.opcfoundation.ua.core.EndpointConfiguration; import org.opcfoundation.ua.core.MessageSecurityMode; @@ -141,6 +143,12 @@ public static void setHandshakeTimeout(long handshakeTimeout) { /** Optional ReverseHello message, if operating in ReverseHello-mode */ ReverseHello rh; + /** + * Tracks if this connection has ever been successfully SessionActivated at least once. Impl note: + * once set to true, shall be never set to false. + */ + AtomicBoolean hasBeenSuccessfullySessionActivated = new AtomicBoolean(false); + /** Timer used for handshake timing */ Timer timer = TimerUtil.getTimer(); @@ -341,7 +349,9 @@ public void addConnectionListener(IConnectionListener listener) { /** {@inheritDoc} */ @Override public synchronized CloseableObject close() { - try { + // clear timeout timer, if exist, otherwise it will keep this object alive GC-wise. + cancelTimeoutTimer(); + try { setState(CloseableObjectState.Closing); } finally { try { @@ -368,6 +378,36 @@ public SocketAddress getRemoteAddress() { if (socket==null) return null; return socket.getRemoteSocketAddress(); } + + /** + * Returns true if this connection has (or had) a Session that was successfully Activated at least + * once. Returns false in other cases. + */ + public boolean hasBeenSuccessfullySessionActivated() { + return hasBeenSuccessfullySessionActivated.get(); + } + + /** + * Returns true if this connection is eligble to be purged if we approach max number of + * connections, false if this connection is valid and should not be purged. Per 1.05 Part 4 + * section 5.5.2 "To protect against misbehaving Clientsand denial of service attacks, the + * Servershall close the oldest unused SecureChannelthat has no Sessionassigned before reaching + * the maximum number of supported SecureChannels.". However, in addition if the connection was + * formed via a ReverseHello, it is not considered to be eligible as server was the one initiating + * the connection. Thus this returns true, if this connection is neither a reverse connection and + * also has not yet been activated by a Session. + */ + public boolean isPurgeEligible() { + return !isReverse() && !hasBeenSuccessfullySessionActivated(); + } + + /** + * Return true if this connection is a reverse connection i.e. where the server send a + * ReverseHello to a Client to initiate the connection. + */ + public boolean isReverse() { + return rh != null; + } /** {@inheritDoc} */ @Override @@ -377,6 +417,12 @@ public void removeConnectionListener(IConnectionListener listener) { cancelTimeoutTimer(); } + private void checkSecureChannelLimit() throws ServiceResultException { + if (secureChannels.size() >= endpointServer.maxSecureChannelsPerConnection) { + throw new ServiceResultException(StatusCodes.Bad_TcpNotEnoughResources); + } + } + /** * @param uri * @return @@ -393,10 +439,12 @@ private String trimUrl(String uri) { *

cancelTimeoutTimer.

*/ protected void cancelTimeoutTimer() { - // Cancel hand-shake time-out - if (timeoutTimer!=null) { - timeoutTimer.cancel(); - timeoutTimer = null; + // Cancel hand-shake time-out, note that this can never be started again, thus it wont hurt if + // this happens multiple times + final TimerTask tmp = timeoutTimer; // multithread-guard + if (tmp != null) { + tmp.cancel(); + timeoutTimer = null; timeout = null; } } @@ -604,7 +652,10 @@ protected void handleHelloMessage(Hello h) throws ServiceResultException { EndpointBindingCollection c = endpointServer.getEndpointBindings(); if (c==null) throw new ServiceResultException(Bad_UnexpectedError); - String url = trimUrl(h.getEndpointUrl()); + + checkSecureChannelLimit(); + + String url = trimUrl(h.getEndpointUrl()); logger.debug("onHello: url={}", url); @@ -703,6 +754,8 @@ protected void handleOpenSecureChannelRequest(InputMessage mb) throws ServiceRes if (req.getRequestType() == SecurityTokenRequestType.Issue) { + checkSecureChannelLimit(); + OpcTcpServerSecureChannel channel = new OpcTcpServerSecureChannel( this, endpointServer.secureChannelCounter.incrementAndGet() ); logger.debug("handleOpenSecureChannelRequest: endpointServer={} SecureChannelId={}", endpointServer, channel.getSecureChannelId()); channel.handleOpenChannel(mb, req); @@ -1172,6 +1225,19 @@ public void run() { public void run() { try { enc.putMessage(msg.getMessage()); + + // If the message was ActivateSessionResponse, which was Good, then mark that this + // connection has a successful activated session (and thus is not removed when we are at + // max connections. If we are at max connections, per 1.05 Part 4 5.5.2: "To protect + // against misbehaving Clients and denial of service attacks, the Server shall close the + // oldest unused SecureChannelthat has no Session assigned before reaching the maximum + // number of supported SecureChannels. ") + if (msg.getMessage() instanceof ActivateSessionResponse) { + ActivateSessionResponse res = (ActivateSessionResponse) msg.getMessage(); + if (res.getResponseHeader().getServiceResult().isGood()) { + hasBeenSuccessfullySessionActivated.set(true); + } + } } catch (ServiceResultException e) { msg.setError( StackUtils.toServiceResultException(e) ); } diff --git a/src/main/java/org/opcfoundation/ua/transport/tcp/nio/SecureInputMessageBuilder.java b/src/main/java/org/opcfoundation/ua/transport/tcp/nio/SecureInputMessageBuilder.java index 166f5e4c..f37caa7a 100644 --- a/src/main/java/org/opcfoundation/ua/transport/tcp/nio/SecureInputMessageBuilder.java +++ b/src/main/java/org/opcfoundation/ua/transport/tcp/nio/SecureInputMessageBuilder.java @@ -77,6 +77,13 @@ public class SecureInputMessageBuilder implements InputMessage { AtomicInteger expectedSequenceNumber; static Logger log = LoggerFactory.getLogger(SecureInputMessageBuilder.class); + /* + * Total number of bytes added via addChunk. This is a long because our max message size is an + * int, so this avoids theoretical overflow cases. Since SecureInputMessageBuilders are not + * re-used, there is no need to reset this counter. + */ + long byteCount = 0; + public interface MessageListener { /** * On message completed or error occured. @@ -172,6 +179,16 @@ public String toString() { public synchronized void addChunk(final ByteBuffer chunk) throws ServiceResultException { if (!acceptsChunks) throw new ServiceResultException(StatusCodes.Bad_UnexpectedError, "Final chunk added to message builder"); + + // prevent messages larger than our max limit + byteCount = byteCount + chunk.remaining(); + log.trace("Current message size via chunks: {}", byteCount); + if (byteCount > encoderCtx.getMaxMessageSize()) { + log.trace("Max message limits ({}) exceeded, at {}, stopping accepting chunks", encoderCtx.getMaxMessageSize(), + byteCount); + throw new ServiceResultException(StatusCodes.Bad_RequestTooLarge); + } + final int chunkNumber = chunksAdded++; chunkSequenceNumbers.add(null); int type = ChunkUtils.getMessageType(chunk); From d3d14f82b6f278311a8c899cd037aa7a38109a21 Mon Sep 17 00:00:00 2001 From: Jouni Aro Date: Fri, 24 Feb 2023 14:12:14 +0200 Subject: [PATCH 15/26] MaxConnectionCount fix verified Tested the change with ServerExample1 and SampleClient. Server was modified to define ResponseHeader, which is required for the limit check to work. Server also throws NotImplemented ServiceFaults for services that are not implemented Changed SampleClient to wait for user interaction before closing the connection --- .../ua/examples/SampleClient.java | 6 ++ .../ua/examples/ServerExample1.java | 91 ++++++++++++------- 2 files changed, 63 insertions(+), 34 deletions(-) diff --git a/examples/basic/src/main/java/org/opcfoundation/ua/examples/SampleClient.java b/examples/basic/src/main/java/org/opcfoundation/ua/examples/SampleClient.java index d1a076de..71c3f4e7 100644 --- a/examples/basic/src/main/java/org/opcfoundation/ua/examples/SampleClient.java +++ b/examples/basic/src/main/java/org/opcfoundation/ua/examples/SampleClient.java @@ -180,6 +180,12 @@ public static void main(String[] args) throws Exception { new ReadValueId(new NodeId(1, "Boolean"), Attributes.Value, null, null)); System.out.println(res4); + // Press enter to shutdown + System.out.println("Enter 'x' to shutdown"); + while (System.in.read() != 'x') { + ; + } + ///////////// SHUTDOWN ///////////// mySession.close(); mySession.closeAsync(); diff --git a/examples/basic/src/main/java/org/opcfoundation/ua/examples/ServerExample1.java b/examples/basic/src/main/java/org/opcfoundation/ua/examples/ServerExample1.java index 3d1d15dd..3a8b96c0 100644 --- a/examples/basic/src/main/java/org/opcfoundation/ua/examples/ServerExample1.java +++ b/examples/basic/src/main/java/org/opcfoundation/ua/examples/ServerExample1.java @@ -85,6 +85,8 @@ import org.opcfoundation.ua.core.ReadValueId; import org.opcfoundation.ua.core.RegisterNodesRequest; import org.opcfoundation.ua.core.RegisterNodesResponse; +import org.opcfoundation.ua.core.RequestHeader; +import org.opcfoundation.ua.core.ResponseHeader; import org.opcfoundation.ua.core.ServiceFault; import org.opcfoundation.ua.core.SessionServiceSetHandler; import org.opcfoundation.ua.core.SignatureData; @@ -118,20 +120,20 @@ public class ServerExample1 { static class MyAttributeServiceHandler implements AttributeServiceSetHandler { @Override - public void onHistoryRead(EndpointServiceRequest req) + public void onHistoryRead(EndpointServiceRequest msgExchange) throws ServiceFaultException { - + throw new ServiceFaultException(ServiceFault.createServiceFault(StatusCodes.Bad_NotImplemented)); } @Override - public void onHistoryUpdate(EndpointServiceRequest req) + public void onHistoryUpdate(EndpointServiceRequest msgExchange) throws ServiceFaultException { - + throw new ServiceFaultException(ServiceFault.createServiceFault(StatusCodes.Bad_NotImplemented)); } @Override - public void onRead(EndpointServiceRequest req) throws ServiceFaultException { - ReadRequest request = req.getRequest(); + public void onRead(EndpointServiceRequest msgExchange) throws ServiceFaultException { + ReadRequest request = msgExchange.getRequest(); ReadValueId[] nodesToRead = request.getNodesToRead(); DataValue[] results = new DataValue[nodesToRead.length]; @@ -149,12 +151,14 @@ public void onRead(EndpointServiceRequest req) throws } } ReadResponse response = new ReadResponse(null, results, null); - req.sendResponse(response); + ResponseHeader responseHeader = createResponseHeader(msgExchange.getRequest().getRequestHeader()); + response.setResponseHeader(responseHeader); + msgExchange.sendResponse(response); } @Override - public void onWrite(EndpointServiceRequest req) throws ServiceFaultException { - + public void onWrite(EndpointServiceRequest msgExchange) throws ServiceFaultException { + throw new ServiceFaultException(ServiceFault.createServiceFault(StatusCodes.Bad_NotImplemented)); } }; @@ -258,6 +262,12 @@ public MyServerExample(Application application) throws Exception { // Peer verifier application.getHttpsSettings().setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); + // Optionally limit the number of connections - note that if you limit sessions, these should + // be at least that big. By default, both values are 100 + + // application.getOpctcpSettings().setMaxConnections(1); + // application.getHttpsSettings().setMaxConnections(1); + // Load Servers's Application Instance Certificate... KeyPair myServerApplicationInstanceCertificate = ExampleKeys.getCert("ServerExample1"); application.addApplicationInstanceCertificate(myServerApplicationInstanceCertificate); @@ -277,15 +287,15 @@ public MyServerExample(Application application) throws Exception { endpointAddress = "opc.https://" + hostname + ":8443/UAExample"; System.out.println(endpointAddress + " bound at " + bindAddress); /* - * Please read specification 1.04 Part 6 section 7.4.1, - * also sign modes can be used in addition to none in HTTPS. + * Please read specification 1.04 Part 6 section 7.4.1, also sign modes can be used in + * addition to none in HTTPS. */ bind(bindAddress, endpointAddress, SecurityMode.NONE); bindAddress = "opc.tcp://" + addr + ":8666/UAExample"; endpointAddress = "opc.tcp://" + hostname + ":8666/UAExample"; System.out.println(endpointAddress + " bound at " + bindAddress); - + Set policies = new HashSet(); policies.add(SecurityPolicy.NONE); policies.addAll(SecurityPolicy.ALL_SECURE_101); @@ -301,53 +311,59 @@ public MyServerExample(Application application) throws Exception { @Override public void onActivateSession(EndpointServiceRequest msgExchange) throws ServiceFaultException { - ActivateSessionResponse res = new ActivateSessionResponse(); - res.setServerNonce(CryptoUtil.createNonce(32)); - res.setResults(new StatusCode[] {StatusCode.GOOD}); - msgExchange.sendResponse(res); + ActivateSessionResponse response = new ActivateSessionResponse(); + ResponseHeader responseHeader = createResponseHeader(msgExchange.getRequest().getRequestHeader()); + response.setResponseHeader(responseHeader); + response.setServerNonce(CryptoUtil.createNonce(32)); + msgExchange.sendResponse(response); } @Override public void onCancel(EndpointServiceRequest msgExchange) throws ServiceFaultException { - + throw new ServiceFaultException(ServiceFault.createServiceFault(StatusCodes.Bad_NotImplemented)); } @Override public void onCloseSession(EndpointServiceRequest msgExchange) throws ServiceFaultException { - CloseSessionResponse res = new CloseSessionResponse(); - msgExchange.sendResponse(res); + CloseSessionResponse response = new CloseSessionResponse(); + ResponseHeader responseHeader = createResponseHeader(msgExchange.getRequest().getRequestHeader()); + response.setResponseHeader(responseHeader); + msgExchange.sendResponse(response); } @Override public void onCreateSession(EndpointServiceRequest msgExchange) throws ServiceFaultException { - CreateSessionRequest req = msgExchange.getRequest(); - CreateSessionResponse res = new CreateSessionResponse(); + CreateSessionRequest request = msgExchange.getRequest(); + CreateSessionResponse response = new CreateSessionResponse(); + ResponseHeader responseHeader = createResponseHeader(msgExchange.getRequest().getRequestHeader()); + response.setResponseHeader(responseHeader); + byte[] token = new byte[32]; byte[] nonce = new byte[32]; Random r = new Random(); r.nextBytes(nonce); r.nextBytes(token); - res.setAuthenticationToken(new NodeId(0, token)); + response.setAuthenticationToken(new NodeId(0, token)); EndpointConfiguration endpointConfiguration = EndpointConfiguration.defaults(); - res.setMaxRequestMessageSize(UnsignedInteger - .valueOf(Math.max(endpointConfiguration.getMaxMessageSize(), req.getMaxResponseMessageSize().longValue()))); - res.setRevisedSessionTimeout(Math.max(req.getRequestedSessionTimeout(), 60 * 1000)); + response.setMaxRequestMessageSize(UnsignedInteger.valueOf( + Math.max(endpointConfiguration.getMaxMessageSize(), request.getMaxResponseMessageSize().longValue()))); + response.setRevisedSessionTimeout(Math.max(request.getRequestedSessionTimeout(), 60 * 1000)); KeyPair cert = getApplication().getApplicationInstanceCertificates()[0]; - res.setServerCertificate(ByteString.valueOf(cert.getCertificate().getEncoded())); - res.setServerEndpoints(this.getEndpointDescriptions()); - res.setServerNonce(ByteString.valueOf(nonce)); - ByteString clientCertificate = req.getClientCertificate(); - ByteString clientNonce = req.getClientNonce(); + response.setServerCertificate(ByteString.valueOf(cert.getCertificate().getEncoded())); + response.setServerEndpoints(this.getEndpointDescriptions()); + response.setServerNonce(ByteString.valueOf(nonce)); + ByteString clientCertificate = request.getClientCertificate(); + ByteString clientNonce = request.getClientNonce(); SecurityPolicy securityPolicy = msgExchange.getChannel().getSecurityPolicy(); - res.setServerSignature( + response.setServerSignature( getServerSignature(clientCertificate, clientNonce, securityPolicy, cert.getPrivateKey().getPrivateKey())); - res.setServerSoftwareCertificates(getApplication().getSoftwareCertificates()); - res.setSessionId(new NodeId(0, "Session-" + UUID.randomUUID())); - msgExchange.sendResponse(res); + response.setServerSoftwareCertificates(getApplication().getSoftwareCertificates()); + response.setSessionId(new NodeId(0, "Session-" + UUID.randomUUID())); + msgExchange.sendResponse(response); } private SignatureData getServerSignature(ByteString clientCertificate, ByteString clientNonce, @@ -381,6 +397,13 @@ private SignatureData getServerSignature(ByteString clientCertificate, ByteStrin } } + public static ResponseHeader createResponseHeader(RequestHeader requestHeader) { + ResponseHeader responseHeader = new ResponseHeader(); + responseHeader.setServiceResult(StatusCode.GOOD); + responseHeader.setRequestHandle(requestHeader.getRequestHandle()); + return responseHeader; + } + public static void main(String[] args) throws Exception { ////////////// SERVER ////////////// // Create UA Server Application From 4578018e63fca0f5450116d44d5410eb9706d514 Mon Sep 17 00:00:00 2001 From: Jouni Aro Date: Fri, 31 Mar 2023 14:10:50 +0300 Subject: [PATCH 16/26] GH #224: Multithreading fix --- .../opcfoundation/ua/transport/impl/ConnectionCollection.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/opcfoundation/ua/transport/impl/ConnectionCollection.java b/src/main/java/org/opcfoundation/ua/transport/impl/ConnectionCollection.java index 267d8e9b..1a0993f4 100644 --- a/src/main/java/org/opcfoundation/ua/transport/impl/ConnectionCollection.java +++ b/src/main/java/org/opcfoundation/ua/transport/impl/ConnectionCollection.java @@ -17,6 +17,7 @@ import java.util.Iterator; import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.CopyOnWriteArraySet; import org.opcfoundation.ua.transport.ConnectionMonitor; import org.opcfoundation.ua.transport.ServerConnection; @@ -27,7 +28,7 @@ */ public class ConnectionCollection implements ConnectionMonitor { - Set connections = new HashSet(); + Set connections = new CopyOnWriteArraySet(); CopyOnWriteArrayList listeners = new CopyOnWriteArrayList(); Object sender; From a8a36264ed8c28d90d6e20e00aba07bf843af0ce Mon Sep 17 00:00:00 2001 From: Jouni Aro Date: Fri, 31 Mar 2023 14:11:47 +0300 Subject: [PATCH 17/26] GH #224: cleaned --- .../opcfoundation/ua/transport/impl/ConnectionCollection.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/org/opcfoundation/ua/transport/impl/ConnectionCollection.java b/src/main/java/org/opcfoundation/ua/transport/impl/ConnectionCollection.java index 1a0993f4..ac877227 100644 --- a/src/main/java/org/opcfoundation/ua/transport/impl/ConnectionCollection.java +++ b/src/main/java/org/opcfoundation/ua/transport/impl/ConnectionCollection.java @@ -13,7 +13,6 @@ package org.opcfoundation.ua.transport.impl; import java.util.Collection; -import java.util.HashSet; import java.util.Iterator; import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; From 4afee3267e5672dc5ba153d528c42ea03d3b8908 Mon Sep 17 00:00:00 2001 From: Jouni Aro Date: Fri, 28 Apr 2023 13:56:52 +0300 Subject: [PATCH 18/26] Fixed #224 A couple of details fixing potential multithread issues in connection handling --- .../ua/transport/tcp/nio/OpcTcpServer.java | 12 ++++++++--- .../tcp/nio/OpcTcpServerConnection.java | 21 +++++++++++++++---- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/opcfoundation/ua/transport/tcp/nio/OpcTcpServer.java b/src/main/java/org/opcfoundation/ua/transport/tcp/nio/OpcTcpServer.java index 1ddba1b7..fab751ad 100644 --- a/src/main/java/org/opcfoundation/ua/transport/tcp/nio/OpcTcpServer.java +++ b/src/main/java/org/opcfoundation/ua/transport/tcp/nio/OpcTcpServer.java @@ -191,9 +191,13 @@ public void onOpen() { if (tmp.size() > maxConnections) { logger.trace("We are at over limit, unable to purge enough old connections, closing this connection"); conn.close(); - } else if (tmp.size() == maxConnections) { - logger.trace("We are exactly at maximum connections (including this connection). " - + "No older connections could be purged. Keeping this connection open."); + } else { + if (tmp.size() == maxConnections) { + logger.trace("We are exactly at maximum connections (including this connection). ") ; + } else { + logger.trace("We are below maximum connection limit"); + } + conn.init(); } }}; ConnectionCollection connections = new ConnectionCollection(this); @@ -297,6 +301,8 @@ public void onClosed(ServiceResultException closeError) { @Override public void onOpen() { }}); + // start listening to messages + conn.init(); //async, do last, others listen on socket state. socketHandle.socket.connect(socketHandle.socketAddress); }catch(IOException e) { diff --git a/src/main/java/org/opcfoundation/ua/transport/tcp/nio/OpcTcpServerConnection.java b/src/main/java/org/opcfoundation/ua/transport/tcp/nio/OpcTcpServerConnection.java index 9172a1d2..a3ac061d 100644 --- a/src/main/java/org/opcfoundation/ua/transport/tcp/nio/OpcTcpServerConnection.java +++ b/src/main/java/org/opcfoundation/ua/transport/tcp/nio/OpcTcpServerConnection.java @@ -311,15 +311,27 @@ public void onStateTransition(IStatefulObject monitor, SocketSta setState(CloseableObjectState.Opening); + } + + /** + * Start listening for data from the connection, note that data might be processed already before + * this method returns. + */ + public void init() { s.getStateMonitor().addStateListener(socketListener); - s.getInputStream().createMonitor(8, inputListener); + // must set timeout timer here, because it might be canceled before the below monitor + // is triggered if(rh == null) { timeoutTimer = TimerUtil.schedule( timer, timeout, StackUtils.getBlockingWorkExecutor(), System.currentTimeMillis() + handshakeTimeout); } + + // Start listening for the Hello (the inputListener will schedule itself again) + s.getInputStream().createMonitor(8, inputListener); + if(rh != null) { s.getStateMonitor().addStateListener(new StateListener() { @Override @@ -335,8 +347,8 @@ public void onStateTransition( } } }); - } - } + } + } /** {@inheritDoc} */ @Override @@ -1234,7 +1246,8 @@ public void run() { // number of supported SecureChannels. ") if (msg.getMessage() instanceof ActivateSessionResponse) { ActivateSessionResponse res = (ActivateSessionResponse) msg.getMessage(); - if (res.getResponseHeader().getServiceResult().isGood()) { + if ((res.getResponseHeader() != null) && (res.getResponseHeader().getServiceResult() != null) && + (res.getResponseHeader().getServiceResult().isGood())) { hasBeenSuccessfullySessionActivated.set(true); } } From df8cabba132693dde37803058bad6620a6e6a192 Mon Sep 17 00:00:00 2001 From: Chirantan Joshi <73328079+jchirantan@users.noreply.github.com> Date: Thu, 25 May 2023 22:27:45 +0530 Subject: [PATCH 19/26] Fix for issue id226 (#230) * Added changes for handling password string for UserIdentityToken and for private key in case of certificate based connections to prevent it's trace in memory New utilities: CryptoUtil.toBytes & ByteBufferUtils.clear New overload to: CertificateUtils.base64Decode that takes byte[] (and respectively to CryptoProvider.base64Decode & CertificateProvider.base64Decode) - that enables dealing with encoded that can be cleared (such as passwords in application code) without strings Co-authored-by: Jouni Aro --- .../ua/application/SessionChannel.java | 6 + .../security/BcCertificateProvider.java | 7 + .../transport/security/BcCryptoProvider.java | 6 + .../security/CertificateProvider.java | 2 + .../ua/transport/security/CryptoProvider.java | 2 + .../transport/security/JceCryptoProvider.java | 6 + .../ua/transport/security/KeyPair.java | 14 ++ .../ua/transport/security/PrivKey.java | 11 ++ .../security/ScCertificateProvider.java | 6 + .../transport/security/ScCryptoProvider.java | 6 + .../ua/utils/CertificateUtils.java | 26 +++- .../opcfoundation/ua/utils/CryptoUtil.java | 28 ++++ .../opcfoundation/ua/utils/EndpointUtil.java | 135 +++++++++++------- .../ua/utils/bytebuffer/ByteBufferUtils.java | 15 +- 14 files changed, 209 insertions(+), 61 deletions(-) diff --git a/src/main/java/org/opcfoundation/ua/application/SessionChannel.java b/src/main/java/org/opcfoundation/ua/application/SessionChannel.java index 17e0d56b..9bd01231 100644 --- a/src/main/java/org/opcfoundation/ua/application/SessionChannel.java +++ b/src/main/java/org/opcfoundation/ua/application/SessionChannel.java @@ -156,6 +156,12 @@ public ActivateSessionResponse activate() * @throws org.opcfoundation.ua.common.ServiceResultException if error */ public ActivateSessionResponse activate(String username, String password) throws ServiceResultException + { + return activate(username, password.toCharArray()); + } + + //Overloaded function to accept password as character array + public ActivateSessionResponse activate(String username, char[] password) throws ServiceResultException { UserIdentityToken token = EndpointUtil.createUserNameIdentityToken(session.getEndpoint(), session.getServerNonce(), username, password); return activate(token, null); diff --git a/src/main/java/org/opcfoundation/ua/transport/security/BcCertificateProvider.java b/src/main/java/org/opcfoundation/ua/transport/security/BcCertificateProvider.java index 56279ea7..67d98ea6 100644 --- a/src/main/java/org/opcfoundation/ua/transport/security/BcCertificateProvider.java +++ b/src/main/java/org/opcfoundation/ua/transport/security/BcCertificateProvider.java @@ -285,6 +285,12 @@ public byte[] base64Decode(String string) { return Base64.decode(StringUtils.removeWhitespace(string)); } + /** {@inheritDoc} */ + @Override + public byte[] base64Decode(byte[] bytes) { + return Base64.decode(bytes); + } + /** {@inheritDoc} */ @Override public String base64Encode(byte[] bytes) { @@ -295,4 +301,5 @@ public String base64Encode(byte[] bytes) { } } + } diff --git a/src/main/java/org/opcfoundation/ua/transport/security/BcCryptoProvider.java b/src/main/java/org/opcfoundation/ua/transport/security/BcCryptoProvider.java index a8d04385..919b0c73 100644 --- a/src/main/java/org/opcfoundation/ua/transport/security/BcCryptoProvider.java +++ b/src/main/java/org/opcfoundation/ua/transport/security/BcCryptoProvider.java @@ -75,6 +75,12 @@ public byte[] base64Decode(String string) { return Base64.decode(StringUtils.removeWhitespace(string)); } + /** {@inheritDoc} */ + @Override + public byte[] base64Decode(byte[] bytes) { + return Base64.decode(bytes); + } + /** {@inheritDoc} */ @Override public String base64Encode(byte[] bytes) { diff --git a/src/main/java/org/opcfoundation/ua/transport/security/CertificateProvider.java b/src/main/java/org/opcfoundation/ua/transport/security/CertificateProvider.java index 5c6a816f..89a1c033 100644 --- a/src/main/java/org/opcfoundation/ua/transport/security/CertificateProvider.java +++ b/src/main/java/org/opcfoundation/ua/transport/security/CertificateProvider.java @@ -32,6 +32,8 @@ public interface CertificateProvider { public byte[] base64Decode(String string); + public byte[] base64Decode(byte[] bytes); + public String base64Encode(byte[] bytes); public X509Certificate generateCertificate(String domainName, diff --git a/src/main/java/org/opcfoundation/ua/transport/security/CryptoProvider.java b/src/main/java/org/opcfoundation/ua/transport/security/CryptoProvider.java index 5a8f6261..9467b19a 100644 --- a/src/main/java/org/opcfoundation/ua/transport/security/CryptoProvider.java +++ b/src/main/java/org/opcfoundation/ua/transport/security/CryptoProvider.java @@ -25,6 +25,8 @@ public interface CryptoProvider { public byte[] base64Decode(String string); + + public byte[] base64Decode(byte[] bytes); public String base64Encode(byte[] bytes); diff --git a/src/main/java/org/opcfoundation/ua/transport/security/JceCryptoProvider.java b/src/main/java/org/opcfoundation/ua/transport/security/JceCryptoProvider.java index bdd73f0c..0b56c0ef 100644 --- a/src/main/java/org/opcfoundation/ua/transport/security/JceCryptoProvider.java +++ b/src/main/java/org/opcfoundation/ua/transport/security/JceCryptoProvider.java @@ -66,6 +66,12 @@ public byte[] base64Decode(String string) { return CertificateUtils.base64Decode(string); } + /** {@inheritDoc} */ + @Override + public byte[] base64Decode(byte[] bytes) { + return CertificateUtils.base64Decode(bytes); + } + /** {@inheritDoc} */ @Override public String base64Encode(byte[] bytes) { diff --git a/src/main/java/org/opcfoundation/ua/transport/security/KeyPair.java b/src/main/java/org/opcfoundation/ua/transport/security/KeyPair.java index 37a03df3..de0e6070 100644 --- a/src/main/java/org/opcfoundation/ua/transport/security/KeyPair.java +++ b/src/main/java/org/opcfoundation/ua/transport/security/KeyPair.java @@ -49,6 +49,13 @@ public final class KeyPair { */ public static KeyPair load(URL certificateFile, URL privateKeyFile, String privateKeyPassword) throws IOException, UnrecoverableKeyException, NoSuchAlgorithmException, CertificateException, KeyStoreException + { + return load(certificateFile, privateKeyFile, privateKeyPassword.toCharArray()); + } + + //Overloaded method to accept password as character array + public static KeyPair load(URL certificateFile, URL privateKeyFile, char[] privateKeyPassword) + throws IOException, UnrecoverableKeyException, NoSuchAlgorithmException, CertificateException, KeyStoreException { Cert cert = Cert.load(certificateFile); PrivKey privKey = PrivKey.loadFromKeyStore(privateKeyFile, privateKeyPassword); @@ -70,6 +77,13 @@ public static KeyPair load(URL certificateFile, URL privateKeyFile, String priva */ public static KeyPair load(File certificateFile, File privateKeyFile, String privateKeyPassword) throws IOException, UnrecoverableKeyException, NoSuchAlgorithmException, CertificateException, KeyStoreException + { + return load(certificateFile, privateKeyFile, privateKeyPassword.toCharArray()); + } + + //Overloaded method to accept password as character array + public static KeyPair load(File certificateFile, File privateKeyFile, char[] privateKeyPassword) + throws IOException, UnrecoverableKeyException, NoSuchAlgorithmException, CertificateException, KeyStoreException { Cert cert = Cert.load(certificateFile); PrivKey privKey = PrivKey.loadFromKeyStore(privateKeyFile, privateKeyPassword); diff --git a/src/main/java/org/opcfoundation/ua/transport/security/PrivKey.java b/src/main/java/org/opcfoundation/ua/transport/security/PrivKey.java index 901b0b01..196e96c7 100644 --- a/src/main/java/org/opcfoundation/ua/transport/security/PrivKey.java +++ b/src/main/java/org/opcfoundation/ua/transport/security/PrivKey.java @@ -74,6 +74,12 @@ public class PrivKey { * @throws java.security.UnrecoverableKeyException if any. */ public static PrivKey loadFromKeyStore(URL keystoreUrl, String password) throws IOException, UnrecoverableKeyException, NoSuchAlgorithmException, CertificateException, KeyStoreException + { + return loadFromKeyStore(keystoreUrl, password.toCharArray()); + } + + //Overloaded method to accept password as character array + public static PrivKey loadFromKeyStore(URL keystoreUrl, char[] password) throws IOException, UnrecoverableKeyException, NoSuchAlgorithmException, CertificateException, KeyStoreException { RSAPrivateKey key = CertificateUtils.loadFromKeyStore(keystoreUrl, password); return new PrivKey(key); @@ -281,6 +287,11 @@ public static PrivKey loadFromKeyStore(File file, String password) throws IOExce return loadFromKeyStore( file.toURI().toURL(), password ); } + //Overloaded method to accept password as character array + public static PrivKey loadFromKeyStore(File file, char[] password) throws IOException, UnrecoverableKeyException, NoSuchAlgorithmException, CertificateException, KeyStoreException + { + return loadFromKeyStore( file.toURI().toURL(), password ); + } /** * Save the key in a binary file. Note that the file is not secured by a password. * diff --git a/src/main/java/org/opcfoundation/ua/transport/security/ScCertificateProvider.java b/src/main/java/org/opcfoundation/ua/transport/security/ScCertificateProvider.java index dfc3275e..032c4b08 100644 --- a/src/main/java/org/opcfoundation/ua/transport/security/ScCertificateProvider.java +++ b/src/main/java/org/opcfoundation/ua/transport/security/ScCertificateProvider.java @@ -280,6 +280,12 @@ public byte[] base64Decode(String string) { return Base64.decode(StringUtils.removeWhitespace(string)); } + /** {@inheritDoc} */ + @Override + public byte[] base64Decode(byte[] bytes) { + return Base64.decode(bytes); + } + /** {@inheritDoc} */ @Override public String base64Encode(byte[] bytes) { diff --git a/src/main/java/org/opcfoundation/ua/transport/security/ScCryptoProvider.java b/src/main/java/org/opcfoundation/ua/transport/security/ScCryptoProvider.java index 18d61493..23eefc8d 100644 --- a/src/main/java/org/opcfoundation/ua/transport/security/ScCryptoProvider.java +++ b/src/main/java/org/opcfoundation/ua/transport/security/ScCryptoProvider.java @@ -75,6 +75,12 @@ public byte[] base64Decode(String string) { return Base64.decode(StringUtils.removeWhitespace(string)); } + /** {@inheritDoc} */ + @Override + public byte[] base64Decode(byte[] bytes) { + return Base64.decode(bytes); + } + /** {@inheritDoc} */ @Override public String base64Encode(byte[] bytes) { diff --git a/src/main/java/org/opcfoundation/ua/utils/CertificateUtils.java b/src/main/java/org/opcfoundation/ua/utils/CertificateUtils.java index bcf49cd2..021069da 100644 --- a/src/main/java/org/opcfoundation/ua/utils/CertificateUtils.java +++ b/src/main/java/org/opcfoundation/ua/utils/CertificateUtils.java @@ -21,7 +21,7 @@ import java.io.InputStream; import java.math.BigInteger; import java.net.URL; -import java.net.URLConnection; +import java.net.URLConnection; import java.security.GeneralSecurityException; import java.security.InvalidKeyException; import java.security.Key; @@ -44,7 +44,7 @@ import java.security.cert.CertificateParsingException; import java.security.cert.X509Certificate; import java.security.interfaces.RSAPrivateKey; -import java.util.Arrays; +import java.util.Arrays; import java.util.Calendar; import java.util.Collection; import java.util.Date; @@ -82,6 +82,16 @@ public static byte[] base64Decode(String string) { return getCertificateProvider().base64Decode(string); } + /** + *

base64Decode.

+ * + * @param bytes the array of bytes to convert. + * @return an array of byte. + */ + public static byte[] base64Decode(byte[] bytes) { + return getCertificateProvider().base64Decode(bytes); + } + /** *

base64Encode a byte array to string

* @@ -234,6 +244,13 @@ public static X509Certificate decodeX509Certificate( public static RSAPrivateKey loadFromKeyStore(URL keystoreUrl, String password) throws IOException, NoSuchAlgorithmException, CertificateException, KeyStoreException, UnrecoverableKeyException { + return loadFromKeyStore(keystoreUrl,password.toCharArray()); + } + + //Overloaded method to accept password as character array + public static RSAPrivateKey loadFromKeyStore(URL keystoreUrl, + char[] password) throws IOException, NoSuchAlgorithmException, + CertificateException, KeyStoreException, UnrecoverableKeyException { logger.debug("loadFromKeyStore: keystoreUrl={}", keystoreUrl); // Open pfx-certificate URLConnection connection = keystoreUrl.openConnection(); @@ -255,13 +272,14 @@ public static RSAPrivateKey loadFromKeyStore(URL keystoreUrl, keyStore = KeyStore.getInstance("PKCS12"); } logger.debug("loadFromKeyStore: keyStore Provider={}", keyStore.getProvider()); - keyStore.load(is, password == null ? null : password.toCharArray()); + + keyStore.load(is, password == null ? null : password); Enumeration aliases = keyStore.aliases(); Key key = null; while (aliases.hasMoreElements()) { String a = (String) aliases.nextElement(); - key = keyStore.getKey(a, password == null ? null : password.toCharArray()); + key = keyStore.getKey(a, password == null ? null : password); } return (RSAPrivateKey) key; diff --git a/src/main/java/org/opcfoundation/ua/utils/CryptoUtil.java b/src/main/java/org/opcfoundation/ua/utils/CryptoUtil.java index 0aecade6..12c9d279 100644 --- a/src/main/java/org/opcfoundation/ua/utils/CryptoUtil.java +++ b/src/main/java/org/opcfoundation/ua/utils/CryptoUtil.java @@ -12,6 +12,9 @@ package org.opcfoundation.ua.utils; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.Charset; import java.security.InvalidKeyException; import java.security.Key; import java.security.NoSuchAlgorithmException; @@ -125,6 +128,15 @@ public static byte[] asymmEncrypt(byte[] input, Key key, public static byte[] base64Decode(String string) { return getCryptoProvider().base64Decode(string); } + + /** + * Overloaded base64Decode Function to accept byte array + * @param bytes the bytes to decode + * @return an array of byte + */ + public static byte[] base64Decode(byte[] bytes) { + return getCryptoProvider().base64Decode(bytes); + } /** *

base64Encode a byte array to string

@@ -718,6 +730,22 @@ public static String toHex(byte[] bytes, int bytesPerRow) { return sb.toString(); } + /** + * Convert Char Array to Byte Array without String to avoid leaving traces of the intermediate results in the memory. + * + * If chars is null, returns an empty byte array. + */ + public static byte[] toBytes(char[] chars) { + if (chars == null) + return new byte[0]; + CharBuffer charBuffer = CharBuffer.wrap(chars); + ByteBuffer byteBuffer = Charset.forName("UTF-8").encode(charBuffer); + byte[] bytes = Arrays.copyOfRange(byteBuffer.array(), + byteBuffer.position(), byteBuffer.limit()); + Arrays.fill(byteBuffer.array(), (byte) 0); // clear sensitive data + return bytes; + } + /** * Verify a signature. * diff --git a/src/main/java/org/opcfoundation/ua/utils/EndpointUtil.java b/src/main/java/org/opcfoundation/ua/utils/EndpointUtil.java index 357c9c2e..48642ea1 100644 --- a/src/main/java/org/opcfoundation/ua/utils/EndpointUtil.java +++ b/src/main/java/org/opcfoundation/ua/utils/EndpointUtil.java @@ -316,7 +316,7 @@ public static void reverse(Object array) { * * @param ep a {@link org.opcfoundation.ua.core.EndpointDescription} object. * @param username a {@link java.lang.String} object. - * @param password a {@link java.lang.String} object. + * @param password the clear text password as {@link java.lang.String}. * @return user identity token * @throws org.opcfoundation.ua.common.ServiceResultException if endpoint or the stack doesn't support UserName token policy * @param byteString an array of byte. @@ -324,60 +324,87 @@ public static void reverse(Object array) { public static UserIdentityToken createUserNameIdentityToken(EndpointDescription ep, ByteString byteString, String username, String password) throws ServiceResultException { - UserTokenPolicy policy = ep.findUserTokenPolicy(UserTokenType.UserName); - if (policy==null) throw new ServiceResultException(StatusCodes.Bad_IdentityTokenRejected, "UserName not supported"); - String securityPolicyUri = policy.getSecurityPolicyUri(); - if (securityPolicyUri==null) securityPolicyUri = ep.getSecurityPolicyUri(); - SecurityPolicy securityPolicy = SecurityPolicy.getSecurityPolicy( securityPolicyUri ); - if (securityPolicy==null) securityPolicy = SecurityPolicy.NONE; - UserNameIdentityToken token = new UserNameIdentityToken(); - - token.setUserName( username ); - token.setPolicyId( policy.getPolicyId() ); - - // Encrypt the password, unless no security is defined - SecurityAlgorithm algorithm = securityPolicy.getAsymmetricEncryptionAlgorithm(); - logger.debug("createUserNameIdentityToken: algorithm={}", algorithm); - byte[] pw = password.getBytes(BinaryEncoder.UTF8); - if (algorithm == null) - token.setPassword(ByteString.valueOf(pw)); - else { - try { - byte[] c = ByteString.asByteArray(ep.getServerCertificate()); - Cert serverCert = (c == null || c.length == 0) ? null : new Cert(c); - if (byteString != null) - pw = ByteBufferUtils.concatenate(toArray(pw.length - + byteString.getLength()), pw, byteString.getValue()); - else - pw = ByteBufferUtils.concatenate(toArray(pw.length), pw); - pw = CryptoUtil.encryptAsymm(pw, serverCert.getCertificate() - .getPublicKey(), algorithm); - token.setPassword(ByteString.valueOf(pw)); - - } catch (InvalidKeyException e) { - // Server certificate does not have encrypt usage - throw new ServiceResultException( - StatusCodes.Bad_CertificateInvalid, - "Server certificate in endpoint is invalid: " - + e.getMessage()); - } catch (IllegalBlockSizeException e) { - throw new ServiceResultException( - StatusCodes.Bad_SecurityPolicyRejected, e.getClass() - .getName() + ":" + e.getMessage()); - } catch (BadPaddingException e) { - throw new ServiceResultException( - StatusCodes.Bad_CertificateInvalid, - "Server certificate in endpoint is invalid: " - + e.getMessage()); - } catch (NoSuchAlgorithmException e) { - throw new ServiceResultException(StatusCodes.Bad_InternalError, e); - } catch (NoSuchPaddingException e) { - throw new ServiceResultException(StatusCodes.Bad_InternalError, e); - } - token.setEncryptionAlgorithm(algorithm.getUri()); - } - return token; + return createUserNameIdentityToken(ep, byteString, username, password == null ? null : password.toCharArray()); } + + /** + * Create user identity token based on username and password + * + * @param ep a {@link org.opcfoundation.ua.core.EndpointDescription} object. + * @param username a {@link java.lang.String} object. + * @param password the clear text password as char array. + * @return user identity token + * @throws org.opcfoundation.ua.common.ServiceResultException if endpoint or the stack doesn't support UserName token policy + * @param byteString an array of byte. + */ + public static UserIdentityToken createUserNameIdentityToken(EndpointDescription ep, ByteString byteString, String username, char[] password) + throws ServiceResultException + { + UserTokenPolicy policy = ep.findUserTokenPolicy(UserTokenType.UserName); + if (policy==null) throw new ServiceResultException(StatusCodes.Bad_IdentityTokenRejected, "UserName not supported"); + String securityPolicyUri = policy.getSecurityPolicyUri(); + if (securityPolicyUri==null) securityPolicyUri = ep.getSecurityPolicyUri(); + SecurityPolicy securityPolicy = SecurityPolicy.getSecurityPolicy( securityPolicyUri ); + if (securityPolicy==null) securityPolicy = SecurityPolicy.NONE; + UserNameIdentityToken token = new UserNameIdentityToken(); + + token.setUserName( username ); + token.setPolicyId( policy.getPolicyId() ); + + // Encrypt the password, unless no security is defined + SecurityAlgorithm algorithm = securityPolicy.getAsymmetricEncryptionAlgorithm(); + logger.debug("createUserNameIdentityToken: algorithm={}", algorithm); + + byte[] pwTemp = CryptoUtil.toBytes(password); + + if (algorithm == null) + { + token.setPassword(ByteString.valueOf(pwTemp)); + //Clear sensitive data from memory + ByteBufferUtils.clear(pwTemp); + } + else { + try { + byte[] pw = null; + byte[] c = ByteString.asByteArray(ep.getServerCertificate()); + Cert serverCert = (c == null || c.length == 0) ? null : new Cert(c); + if (byteString != null) + pw = ByteBufferUtils.concatenate(toArray(pwTemp.length + + byteString.getLength()), pwTemp, byteString.getValue()); + else + pw = ByteBufferUtils.concatenate(toArray(pwTemp.length), pwTemp); + //Clear sensitive data from memory + ByteBufferUtils.clear(pwTemp); + byte[] pw1 = CryptoUtil.encryptAsymm(pw, serverCert.getCertificate() + .getPublicKey(), algorithm); + token.setPassword(ByteString.valueOf(pw1)); + //Clear sensitive data from memory + ByteBufferUtils.clear(pw); + + } catch (InvalidKeyException e) { + // Server certificate does not have encrypt usage + throw new ServiceResultException( + StatusCodes.Bad_CertificateInvalid, + "Server certificate in endpoint is invalid: " + + e.getMessage()); + } catch (IllegalBlockSizeException e) { + throw new ServiceResultException( + StatusCodes.Bad_SecurityPolicyRejected, e.getClass() + .getName() + ":" + e.getMessage()); + } catch (BadPaddingException e) { + throw new ServiceResultException( + StatusCodes.Bad_CertificateInvalid, + "Server certificate in endpoint is invalid: " + + e.getMessage()); + } catch (NoSuchAlgorithmException e) { + throw new ServiceResultException(StatusCodes.Bad_InternalError, e); + } catch (NoSuchPaddingException e) { + throw new ServiceResultException(StatusCodes.Bad_InternalError, e); + } + token.setEncryptionAlgorithm(algorithm.getUri()); + } + return token; + } /** * Create user identity token based on an issued token diff --git a/src/main/java/org/opcfoundation/ua/utils/bytebuffer/ByteBufferUtils.java b/src/main/java/org/opcfoundation/ua/utils/bytebuffer/ByteBufferUtils.java index a0c727c8..42c6b18f 100644 --- a/src/main/java/org/opcfoundation/ua/utils/bytebuffer/ByteBufferUtils.java +++ b/src/main/java/org/opcfoundation/ua/utils/bytebuffer/ByteBufferUtils.java @@ -13,11 +13,10 @@ package org.opcfoundation.ua.utils.bytebuffer; import java.nio.ByteBuffer; +import java.util.Arrays; /** *

ByteBufferUtils class.

- * - * @author Toni Kalajainen (toni.kalajainen@vtt.fi) */ public class ByteBufferUtils { @@ -50,7 +49,7 @@ public static void copy(ByteBuffer src, ByteBuffer dst, int length) src.limit(srcLimit); dst.limit(dstLimit); } - + /** * Concatenate two arrays to one * @@ -72,5 +71,15 @@ public static byte[] concatenate(byte[]...chunks) return result; } + /** + * Fill the byte array with 0 values. + * + * @param value an array of byte. + */ + public static void clear(byte[] value) + { + Arrays.fill(value, (byte)0); + } + } From b0e51cd8469b43d3d66027e9fa962bc57ed0c38a Mon Sep 17 00:00:00 2001 From: Jouni Aro Date: Tue, 7 May 2024 12:47:35 +0300 Subject: [PATCH 20/26] Fixed #235 - Bouncy Castle dependency to 1.78.1 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index fb2f32ec..45fd5f56 100644 --- a/pom.xml +++ b/pom.xml @@ -221,7 +221,7 @@ org.bouncycastle bcpkix-jdk15to18 - 1.64 + 1.78.1 true From 162f763ff449829f297ea0ccb1cab2d456ed023a Mon Sep 17 00:00:00 2001 From: Jouni Aro Date: Thu, 30 May 2024 17:17:19 +0300 Subject: [PATCH 21/26] Fixed #237: Check array lengths in decoders --- .../ua/encoding/binary/BinaryDecoder.java | 12 ++++++ .../ua/encoding/xml/XmlDecoder.java | 38 +++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/src/main/java/org/opcfoundation/ua/encoding/binary/BinaryDecoder.java b/src/main/java/org/opcfoundation/ua/encoding/binary/BinaryDecoder.java index 03bb6bea..99b0207e 100644 --- a/src/main/java/org/opcfoundation/ua/encoding/binary/BinaryDecoder.java +++ b/src/main/java/org/opcfoundation/ua/encoding/binary/BinaryDecoder.java @@ -464,6 +464,7 @@ public T get(String fieldName, Class clazz) throws DecodingException { //handle 1-dim array if(dims == 1) { final int len = getInt32(null); + assertArrayLength(len); if(len == -1) { //null array return null; @@ -498,6 +499,7 @@ public T get(String fieldName, Class clazz) throws DecodingException { //NOTE that if any is 0, total is 0, which is what spec says. totalElements = totalElements * dimLen; } + assertArrayLength(totalElements); //can cast; actual type does not change Object[] arr = (Object[]) Array.newInstance(componentType, totalElements); @SuppressWarnings("unchecked") @@ -510,6 +512,16 @@ public T get(String fieldName, Class clazz) throws DecodingException { return r; } + private void assertArrayLength(int len) throws DecodingException { + if (len < -1) { + throw new DecodingException(StatusCodes.Bad_DecodingError, "Illegal array length " + len); + } + int maxLen = ctx.getMaxArrayLength(); + if (maxLen > 0 && len > maxLen) { + throw new DecodingException(StatusCodes.Bad_EncodingLimitsExceeded, "MaxArrayLength=" + maxLen + " < " + len); + } + } + /** {@inheritDoc} */ @Override public Object getArrayObject(String fieldName, int builtinTypeId) diff --git a/src/main/java/org/opcfoundation/ua/encoding/xml/XmlDecoder.java b/src/main/java/org/opcfoundation/ua/encoding/xml/XmlDecoder.java index a78f1bab..c8a9f007 100755 --- a/src/main/java/org/opcfoundation/ua/encoding/xml/XmlDecoder.java +++ b/src/main/java/org/opcfoundation/ua/encoding/xml/XmlDecoder.java @@ -432,6 +432,7 @@ public Boolean[] getBooleanArray(String fieldName) throws DecodingException while (moveToElement("Boolean")) { values.add(getBoolean("Boolean")); + assertArrayLength(values.size()); } // check the length. @@ -487,6 +488,7 @@ public UnsignedByte[] getByteArray(String fieldName) throws DecodingException while (moveToElement("Byte")) { values.add(getByte("Byte")); + assertArrayLength(values.size()); } // check the length. @@ -570,6 +572,7 @@ public ByteString[] getByteStringArray(String fieldName) throws DecodingExceptio while (moveToElement("ByteString")) { values.add(getByteString("ByteString")); + assertArrayLength(values.size()); } // check the length. @@ -630,6 +633,7 @@ public DataValue[] getDataValueArray(String fieldName) throws DecodingException while (moveToElement("DataValue")) { values.add(getDataValue("DataValue")); + assertArrayLength(values.size()); } // check the length. @@ -695,6 +699,7 @@ public DateTime[] getDateTimeArray(String fieldName) throws DecodingException while (moveToElement("DateTime")) { values.add(getDateTime("DateTime")); + assertArrayLength(values.size()); } // check the length. @@ -798,6 +803,7 @@ public DiagnosticInfo[] getDiagnosticInfoArray(String fieldName) throws Decoding while (moveToElement("DiagnosticInfo")) { values.add(getDiagnosticInfo("DiagnosticInfo")); + assertArrayLength(values.size()); } // check the length. @@ -879,6 +885,7 @@ public Double[] getDoubleArray(String fieldName) throws DecodingException while (moveToElement("Double")) { values.add(getDouble("Double")); + assertArrayLength(values.size()); } // check the length. @@ -977,6 +984,7 @@ public T[] getEncodeableArray(String fieldName, Class T[] getEnumerationArray(String fieldName, Class 0 && len > maxLen) { + throw new DecodingException(StatusCodes.Bad_EncodingLimitsExceeded, "MaxArrayLength=" + maxLen + " < " + len); + } + } + private boolean isDecimal(ExtensionObject value) { return getEncoderContext().getNamespaceTable().nodeIdEquals(Identifiers.Decimal, value.getTypeId()); } From 50752022047121c029e424c4b9cc1cbfe8845573 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 30 May 2024 17:21:42 +0300 Subject: [PATCH 22/26] Bump guava from 31.0.1-jre to 32.0.0-jre in /codegen (#231) Bumps [guava](https://github.com/google/guava) from 31.0.1-jre to 32.0.0-jre. - [Release notes](https://github.com/google/guava/releases) - [Commits](https://github.com/google/guava/commits) --- updated-dependencies: - dependency-name: com.google.guava:guava dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- codegen/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codegen/pom.xml b/codegen/pom.xml index 63dc1f98..67ba56fc 100644 --- a/codegen/pom.xml +++ b/codegen/pom.xml @@ -33,7 +33,7 @@ com.google.guava guava - 31.0.1-jre + 32.0.0-jre \ No newline at end of file From 4f2cbd2c368fadc893f8d7b9e0d391122ddd0e37 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 30 May 2024 17:36:07 +0300 Subject: [PATCH 23/26] Bump org.bouncycastle:bcpkix-jdk15to18 in /examples/basic (#238) Bumps [org.bouncycastle:bcpkix-jdk15to18](https://github.com/bcgit/bc-java) from 1.64 to 1.78. - [Changelog](https://github.com/bcgit/bc-java/blob/main/docs/releasenotes.html) - [Commits](https://github.com/bcgit/bc-java/commits) --- updated-dependencies: - dependency-name: org.bouncycastle:bcpkix-jdk15to18 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/basic/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/basic/pom.xml b/examples/basic/pom.xml index 8e688609..c6c9bd0a 100644 --- a/examples/basic/pom.xml +++ b/examples/basic/pom.xml @@ -39,7 +39,7 @@ org.bouncycastle bcpkix-jdk15to18 - 1.64 + 1.78 runtime From 63e496496a65d6113ee67fbc5878dca202c7eff0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 30 May 2024 17:43:18 +0300 Subject: [PATCH 24/26] Bump ch.qos.logback:logback-core from 1.2.9 to 1.2.13 in /examples/basic (#240) Bumps [ch.qos.logback:logback-core](https://github.com/qos-ch/logback) from 1.2.9 to 1.2.13. - [Commits](https://github.com/qos-ch/logback/compare/v_1.2.9...v_1.2.13) --- updated-dependencies: - dependency-name: ch.qos.logback:logback-core dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/basic/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/basic/pom.xml b/examples/basic/pom.xml index c6c9bd0a..ed43d17b 100644 --- a/examples/basic/pom.xml +++ b/examples/basic/pom.xml @@ -22,7 +22,7 @@ ch.qos.logback logback-core - 1.2.9 + 1.2.13 runtime From 221fb3f196b336debfa9aa53cda6996845aee8df Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 30 May 2024 17:43:32 +0300 Subject: [PATCH 25/26] Bump ch.qos.logback:logback-classic in /examples/basic (#239) Bumps [ch.qos.logback:logback-classic](https://github.com/qos-ch/logback) from 1.2.9 to 1.2.13. - [Commits](https://github.com/qos-ch/logback/compare/v_1.2.9...v_1.2.13) --- updated-dependencies: - dependency-name: ch.qos.logback:logback-classic dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/basic/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/basic/pom.xml b/examples/basic/pom.xml index ed43d17b..b15cb82c 100644 --- a/examples/basic/pom.xml +++ b/examples/basic/pom.xml @@ -28,7 +28,7 @@ ch.qos.logback logback-classic - 1.2.9 + 1.2.13 runtime From d5d4a2c6b7ef578582f37a59d48ccb1ecc2217a8 Mon Sep 17 00:00:00 2001 From: Ravi-Prabhakar <76731361+Ravi-Prabhakar@users.noreply.github.com> Date: Mon, 27 Jan 2025 15:26:47 +0530 Subject: [PATCH 26/26] Fix for issues reported by security scan app on OPCFoundation UA-Java-Legacy repo. (#246) 'final' added to 'clone'-methods to satisfy security scans. --- .../java/org/opcfoundation/ua/builtintypes/DataValue.java | 4 +++- .../opcfoundation/ua/transport/TransportChannelSettings.java | 4 +++- .../ua/transport/tcp/impl/TcpConnectionParameters.java | 4 +++- .../org/opcfoundation/ua/transport/tcp/io/OpcTcpSettings.java | 4 +++- .../ua/transport/tcp/io/TcpConnectionLimits.java | 4 +++- 5 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/opcfoundation/ua/builtintypes/DataValue.java b/src/main/java/org/opcfoundation/ua/builtintypes/DataValue.java index 1a29f500..cb13284e 100644 --- a/src/main/java/org/opcfoundation/ua/builtintypes/DataValue.java +++ b/src/main/java/org/opcfoundation/ua/builtintypes/DataValue.java @@ -274,7 +274,9 @@ public String toString() { /** {@inheritDoc} */ @Override - public Object clone() { + //Git issue 245 + //Suggestion provided by CWE link (http://cwe.mitre.org/data/definitions/491.html) + public final Object clone() { return new DataValue(getValue(), getStatusCode(), getSourceTimestamp(), getServerPicoseconds(), diff --git a/src/main/java/org/opcfoundation/ua/transport/TransportChannelSettings.java b/src/main/java/org/opcfoundation/ua/transport/TransportChannelSettings.java index e08501dd..a45a094e 100644 --- a/src/main/java/org/opcfoundation/ua/transport/TransportChannelSettings.java +++ b/src/main/java/org/opcfoundation/ua/transport/TransportChannelSettings.java @@ -208,7 +208,9 @@ public void readFrom(TransportChannelSettings tcs) { /** {@inheritDoc} */ @Override - public TransportChannelSettings clone() { + //Git issue 245 + //Suggestion provided by CWE link (http://cwe.mitre.org/data/definitions/491.html) + public final TransportChannelSettings clone() { TransportChannelSettings result = new TransportChannelSettings(); if (description!=null) result.setDescription(description.clone()); diff --git a/src/main/java/org/opcfoundation/ua/transport/tcp/impl/TcpConnectionParameters.java b/src/main/java/org/opcfoundation/ua/transport/tcp/impl/TcpConnectionParameters.java index 918109cb..31805d34 100644 --- a/src/main/java/org/opcfoundation/ua/transport/tcp/impl/TcpConnectionParameters.java +++ b/src/main/java/org/opcfoundation/ua/transport/tcp/impl/TcpConnectionParameters.java @@ -31,7 +31,9 @@ public class TcpConnectionParameters implements Cloneable { /** {@inheritDoc} */ @Override - public TcpConnectionParameters clone() { + //Git issue 245 + //Suggestion provided by CWE link (http://cwe.mitre.org/data/definitions/491.html) + public final TcpConnectionParameters clone() { try { return (TcpConnectionParameters) super.clone(); } catch (CloneNotSupportedException e) { diff --git a/src/main/java/org/opcfoundation/ua/transport/tcp/io/OpcTcpSettings.java b/src/main/java/org/opcfoundation/ua/transport/tcp/io/OpcTcpSettings.java index 9ecefb17..cc51bf9d 100644 --- a/src/main/java/org/opcfoundation/ua/transport/tcp/io/OpcTcpSettings.java +++ b/src/main/java/org/opcfoundation/ua/transport/tcp/io/OpcTcpSettings.java @@ -191,7 +191,9 @@ public void readFrom(OpcTcpSettings tcs) { /** {@inheritDoc} */ @Override - public OpcTcpSettings clone() { + //Git issue 245 + //Suggestion provided by CWE link (http://cwe.mitre.org/data/definitions/491.html) + public final OpcTcpSettings clone() { OpcTcpSettings result = new OpcTcpSettings(); result.setClientCertificate(clientCertificate); diff --git a/src/main/java/org/opcfoundation/ua/transport/tcp/io/TcpConnectionLimits.java b/src/main/java/org/opcfoundation/ua/transport/tcp/io/TcpConnectionLimits.java index a99a2b3d..623856e7 100644 --- a/src/main/java/org/opcfoundation/ua/transport/tcp/io/TcpConnectionLimits.java +++ b/src/main/java/org/opcfoundation/ua/transport/tcp/io/TcpConnectionLimits.java @@ -28,7 +28,9 @@ public class TcpConnectionLimits implements Cloneable { /** {@inheritDoc} */ @Override - public TcpConnectionLimits clone() { + //Git issue 245 + //Suggestion provided by CWE link (http://cwe.mitre.org/data/definitions/491.html) + public final TcpConnectionLimits clone() { try { return (TcpConnectionLimits) super.clone(); } catch (CloneNotSupportedException e) {