You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@plc4x.apache.org by jf...@apache.org on 2020/03/10 21:32:23 UTC
[plc4x] branch feature/PLC4X-185-cert-support-opc-ua updated:
[OPCUA] Finished work on Certificate integartion.
This is an automated email from the ASF dual-hosted git repository.
jfeinauer pushed a commit to branch feature/PLC4X-185-cert-support-opc-ua
in repository https://gitbox.apache.org/repos/asf/plc4x.git
The following commit(s) were added to refs/heads/feature/PLC4X-185-cert-support-opc-ua by this push:
new a6da491 [OPCUA] Finished work on Certificate integartion.
a6da491 is described below
commit a6da491ab2d53324c6b578d36205179c241e208c
Author: Julian Feinauer <j....@pragmaticminds.de>
AuthorDate: Tue Mar 10 22:32:11 2020 +0100
[OPCUA] Finished work on Certificate integartion.
---
plc4j/drivers/opcua/pom.xml | 54 ++++++-------
.../apache/plc4x/java/opcua/OpcuaPlcDriver.java | 2 +-
.../opcua/connection/BaseOpcuaPlcConnection.java | 31 ++++++-
.../java/opcua/connection/KeyStoreLoader.java | 94 ++++++++++++++++++++--
.../opcua/connection/OpcuaTcpPlcConnection.java | 57 +++++++++----
5 files changed, 181 insertions(+), 57 deletions(-)
diff --git a/plc4j/drivers/opcua/pom.xml b/plc4j/drivers/opcua/pom.xml
index 7150836..54e87c3 100644
--- a/plc4j/drivers/opcua/pom.xml
+++ b/plc4j/drivers/opcua/pom.xml
@@ -17,7 +17,8 @@
specific language governing permissions and limitations
under the License.
-->
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
@@ -60,23 +61,16 @@
<version>0.3.6</version>
</dependency>
<dependency>
- <groupId>org.eclipse.milo</groupId>
- <artifactId>server-examples</artifactId>
- <version>0.3.6</version>
- </dependency>
-
- <dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
-
- <dependency>
- <groupId>org.osgi</groupId>
- <artifactId>osgi.cmpn</artifactId>
- <version>6.0.0</version>
- <scope>provided</scope>
- </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>osgi.cmpn</artifactId>
+ <version>6.0.0</version>
+ <scope>provided</scope>
+ </dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
@@ -89,22 +83,22 @@
<version>${bouncycastle.version}</version>
</dependency>
-</dependencies>
+ </dependencies>
-<!--
-<build>
- <plugins>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-dependency-plugin</artifactId>
- <configuration>
- <usedDependencies combine.children="append">
- <usedDependency>org.eclipse.milo:sdk-client</usedDependency>
- </usedDependencies>
- </configuration>
- </plugin>
- </plugins>
-</build>
--->
+ <!--
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-dependency-plugin</artifactId>
+ <configuration>
+ <usedDependencies combine.children="append">
+ <usedDependency>org.eclipse.milo:sdk-client</usedDependency>
+ </usedDependencies>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ -->
</project>
\ No newline at end of file
diff --git a/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/OpcuaPlcDriver.java b/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/OpcuaPlcDriver.java
index 9f3de51..54e51dd 100644
--- a/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/OpcuaPlcDriver.java
+++ b/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/OpcuaPlcDriver.java
@@ -47,7 +47,7 @@ public class OpcuaPlcDriver implements PlcDriver {
public static final Pattern INET_ADDRESS_PATTERN = Pattern.compile("tcp://(?<host>[\\w.-]+)(:(?<port>\\d*))?");
public static final Pattern OPCUA_URI_PARAM_PATTERN = Pattern.compile("(?<param>[(\\?|\\&)([^=]+)\\=([^&]+)]+)?"); //later used for regex filtering of the params
- public static final Pattern OPCUA_URI_PATTERN = Pattern.compile("^opcua:(" + INET_ADDRESS_PATTERN + ")?" + "(?<params>[\\w/=?&]+)?");
+ public static final Pattern OPCUA_URI_PATTERN = Pattern.compile("^opcua:(" + INET_ADDRESS_PATTERN + ")?" + "(?<params>[\\w/=?&.\\d*-_]+)?");
private static final int requestTimeout = 10000;
private OpcuaConnectionFactory opcuaConnectionFactory;
diff --git a/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/connection/BaseOpcuaPlcConnection.java b/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/connection/BaseOpcuaPlcConnection.java
index ac6fba4..5ca6783 100644
--- a/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/connection/BaseOpcuaPlcConnection.java
+++ b/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/connection/BaseOpcuaPlcConnection.java
@@ -26,6 +26,7 @@ import org.apache.plc4x.java.api.messages.PlcWriteRequest;
import org.apache.plc4x.java.base.connection.AbstractPlcConnection;
import org.apache.plc4x.java.base.messages.*;
import org.apache.plc4x.java.opcua.protocol.OpcuaPlcFieldHandler;
+import org.eclipse.milo.opcua.stack.core.security.SecurityPolicy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -38,13 +39,15 @@ public abstract class BaseOpcuaPlcConnection extends AbstractPlcConnection imple
protected boolean skipDiscovery = false;
protected String username = null;
protected String password = null;
- protected String certFile;
+ protected String certFile = null;
+ protected SecurityPolicy securityPolicy = null;
+ protected String keyStoreFile = null;
+// protected String keyStorePassword;
/**
* @param params
*/
BaseOpcuaPlcConnection(String params) {
-
if (!StringUtils.isEmpty(params)) {
for (String param : params.split("&")) {
String[] paramElements = param.split("=");
@@ -53,16 +56,38 @@ public abstract class BaseOpcuaPlcConnection extends AbstractPlcConnection imple
String paramValue = paramElements[1];
switch (paramName) {
case "discovery":
- skipDiscovery = !Boolean.valueOf(paramValue);
+ skipDiscovery = !Boolean.parseBoolean(paramValue);
+ logger.debug("Found Parameter 'skipDiscovery' with value {}", this.skipDiscovery);
break;
case "username":
username = paramValue;
+ logger.debug("Found Parameter 'username' with value {}", username);
break;
case "password":
password = paramValue;
+ logger.debug("Found Parameter 'password' with value {}", password);
break;
case "certFile":
certFile = paramValue;
+ logger.debug("Found Parameter 'certFile' with value {}", certFile);
+ break;
+ case "securityPolicy":
+ logger.debug("Got value for security policy: '{}', trying to parse", paramValue);
+ try {
+ securityPolicy = SecurityPolicy.valueOf(paramValue);
+ logger.debug("Using Security Policy {}", securityPolicy);
+ } catch (IllegalArgumentException e) {
+ logger.warn("Unable to parse policy {}", paramValue);
+ }
+ break;
+ case "keyStoreFile":
+ keyStoreFile = paramValue;
+ logger.debug("Found Parameter 'keyStoreFile' with value {}", keyStoreFile);
+ break;
+// case "keyStorePassword":
+// keyStorePassword = paramValue;
+// logger.debug("Found Parameter 'keyStorePassword' with value {}", keyStorePassword);
+// break;
default:
logger.debug("Unknown parameter {} with value {}", paramName, paramValue);
}
diff --git a/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/connection/KeyStoreLoader.java b/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/connection/KeyStoreLoader.java
index 821f58d..abd2d48 100644
--- a/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/connection/KeyStoreLoader.java
+++ b/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/connection/KeyStoreLoader.java
@@ -1,6 +1,6 @@
package org.apache.plc4x.java.opcua.connection;
-import org.eclipse.milo.opcua.sdk.server.util.HostnameUtil;
+import com.google.common.collect.Sets;
import org.eclipse.milo.opcua.stack.core.util.SelfSignedCertificateBuilder;
import org.eclipse.milo.opcua.stack.core.util.SelfSignedCertificateGenerator;
import org.slf4j.Logger;
@@ -8,6 +8,11 @@ import org.slf4j.LoggerFactory;
import java.io.InputStream;
import java.io.OutputStream;
+import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.net.UnknownHostException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.Key;
@@ -16,6 +21,11 @@ import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.X509Certificate;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
import java.util.regex.Pattern;
/**
@@ -27,18 +37,47 @@ public class KeyStoreLoader {
private static final Pattern IP_ADDR_PATTERN = Pattern.compile(
"^(([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.){3}([01]?\\d\\d?|2[0-4]\\d|25[0-5])$");
- private static final String CLIENT_ALIAS = "client-ai";
- private static final char[] PASSWORD = "password".toCharArray();
+ private static final String CLIENT_ALIAS = "client-cert";
+ private static final char[] PASSWORD = "plc4x".toCharArray();
private final Logger logger = LoggerFactory.getLogger(getClass());
private X509Certificate clientCertificate;
private KeyPair clientKeyPair;
+// KeyStoreLoader load(Path baseDir) throws Exception {
+// return load(baseDir, true);
+// }
+//
+// KeyStoreLoader load(Path baseDir, boolean resolve) throws Exception {
+// return load(baseDir, null, resolve);
+// }
+//
+// KeyStoreLoader load(Path file, String password) throws KeyStoreException, IOException, CertificateException, NoSuchAlgorithmException, UnrecoverableKeyException {
+// KeyStore keyStore = KeyStore.getInstance("PKCS12");
+//
+// Path serverKeyStore = file;
+//
+// logger.info("Loading KeyStore at {}", serverKeyStore);
+//
+// try (InputStream in = Files.newInputStream(serverKeyStore)) {
+// keyStore.load(in, password.toCharArray());
+// }
+//
+// Key serverPrivateKey = keyStore.getKey(CLIENT_ALIAS, password.toCharArray());
+// if (serverPrivateKey instanceof PrivateKey) {
+// clientCertificate = (X509Certificate) keyStore.getCertificate(CLIENT_ALIAS);
+// PublicKey serverPublicKey = clientCertificate.getPublicKey();
+// clientKeyPair = new KeyPair(serverPublicKey, (PrivateKey) serverPrivateKey);
+// }
+//
+// return this;
+// }
+
KeyStoreLoader load(Path baseDir) throws Exception {
KeyStore keyStore = KeyStore.getInstance("PKCS12");
- Path serverKeyStore = baseDir.resolve("example-client.pfx");
+ Path serverKeyStore = baseDir;
logger.info("Loading KeyStore at {}", serverKeyStore);
@@ -54,12 +93,13 @@ public class KeyStoreLoader {
.setLocalityName("Folsom")
.setStateName("CA")
.setCountryCode("US")
- .setApplicationUri("urn:eclipse:milo:examples:client")
+// .setApplicationUri("urn:eclipse:milo:examples:client")
+ .setApplicationUri("urn:plc4x-client")
.addDnsName("localhost")
.addIpAddress("127.0.0.1");
// Get as many hostnames and IP addresses as we can listed in the certificate.
- for (String hostname : HostnameUtil.getHostnames("0.0.0.0")) {
+ for (String hostname : getHostnames("0.0.0.0", true)) {
if (IP_ADDR_PATTERN.matcher(hostname).matches()) {
builder.addIpAddress(hostname);
} else {
@@ -97,4 +137,46 @@ public class KeyStoreLoader {
return clientKeyPair;
}
+ public Set<String> getHostnames(String address, boolean includeLoopback) {
+ HashSet hostnames = Sets.newHashSet();
+
+ try {
+ InetAddress inetAddress = InetAddress.getByName(address);
+ if (inetAddress.isAnyLocalAddress()) {
+ try {
+ Enumeration<NetworkInterface> nis = NetworkInterface.getNetworkInterfaces();
+ Iterator var5 = Collections.list(nis).iterator();
+
+ while(var5.hasNext()) {
+ NetworkInterface ni = (NetworkInterface)var5.next();
+ Collections.list(ni.getInetAddresses()).forEach((ia) -> {
+ if (ia instanceof Inet4Address) {
+ boolean loopback = ia.isLoopbackAddress();
+ if (!loopback || includeLoopback) {
+ hostnames.add(ia.getHostName());
+ hostnames.add(ia.getHostAddress());
+ hostnames.add(ia.getCanonicalHostName());
+ }
+ }
+
+ });
+ }
+ } catch (SocketException var7) {
+ logger.warn("Failed to NetworkInterfaces for bind address: {}", address, var7);
+ }
+ } else {
+ boolean loopback = inetAddress.isLoopbackAddress();
+ if (!loopback || includeLoopback) {
+ hostnames.add(inetAddress.getHostName());
+ hostnames.add(inetAddress.getHostAddress());
+ hostnames.add(inetAddress.getCanonicalHostName());
+ }
+ }
+ } catch (UnknownHostException var8) {
+ logger.warn("Failed to get InetAddress for bind address: {}", address, var8);
+ }
+
+ return hostnames;
+ }
+
}
diff --git a/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/connection/OpcuaTcpPlcConnection.java b/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/connection/OpcuaTcpPlcConnection.java
index 76db3c0..d1bbf62 100644
--- a/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/connection/OpcuaTcpPlcConnection.java
+++ b/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/connection/OpcuaTcpPlcConnection.java
@@ -198,11 +198,14 @@ public class OpcuaTcpPlcConnection extends BaseOpcuaPlcConnection {
}
}
+
endpoint = endpoints.stream()
.filter(e -> e.getSecurityPolicyUri().equals(getSecurityPolicy().getUri()))
.filter(endpointFilter())
.findFirst()
- .orElseThrow(() -> new PlcConnectionException("No desired endpoints from"));
+ .orElseThrow(() -> new PlcConnectionException("No desired endpoints found (with right policy)"));
+
+ logger.debug("Using Endpoint {}", endpoint);
// Security
Path securityTempDir = Paths.get(System.getProperty("java.io.tmpdir"), "security");
@@ -219,15 +222,20 @@ public class OpcuaTcpPlcConnection extends BaseOpcuaPlcConnection {
KeyStoreLoader loader;
try {
- loader = new KeyStoreLoader().load(securityTempDir);
+ if (this.keyStoreFile == null) {
+ logger.debug("No KeyStoreFile given, generating...");
+ loader = new KeyStoreLoader().load(securityTempDir.resolve("client-cert.pfx"));
+ } else {
+ logger.debug("KeyStoreFile {} given, loading or generating there...", this.keyStoreFile);
+ loader = new KeyStoreLoader().load(Paths.get(keyStoreFile));
+ }
} catch (Exception e) {
throw new PlcConnectionException("Unable to load Security!", e);
}
- SecurityPolicy securityPolicy = SecurityPolicy.None;
-
+ // Make Security Configurable
logger.info("Using endpoint: {} [{}/{}]",
- endpoint.getEndpointUrl(), securityPolicy, endpoint.getSecurityMode());
+ endpoint.getEndpointUrl(), getSecurityPolicy(), endpoint.getSecurityMode());
// End Security
if (this.skipDiscovery) {
@@ -277,8 +285,9 @@ public class OpcuaTcpPlcConnection extends BaseOpcuaPlcConnection {
OpcUaClientConfig config = OpcUaClientConfig.builder()
.setApplicationName(LocalizedText.english("eclipse milo opc-ua client of the apache PLC4X:PLC4J project"))
- .setApplicationUri("urn:eclipse:milo:plc4x:client")
+ .setApplicationUri("urn:plc4x-client")
.setCertificate(loader.getClientCertificate())
+ .setKeyPair(loader.getClientKeyPair())
.setEndpoint(endpoint)
.setIdentityProvider(getIdentityProvider())
.setRequestTimeout(UInteger.valueOf(requestTimeout))
@@ -298,6 +307,22 @@ public class OpcuaTcpPlcConnection extends BaseOpcuaPlcConnection {
}
}
+// private X509Certificate getClientCertificate(KeyStoreLoader loader) {
+// if (this.certFile != null) {
+// Path path = null;
+// try {
+// path = Paths.get(this.certFile);
+// final X509Certificate x509Certificate = CertificateUtil.decodeCertificate(Files.readAllBytes(path));
+// logger.info("Using Certificate given by certFile as Client Certificate");
+// return x509Certificate;
+// } catch (UaException | IOException e) {
+// logger.warn("Unable to load given Certificate File {}", path != null ? path.toAbsolutePath().toString() : this.certFile, e);
+// }
+// }
+// logger.info("Using self signed generated Client Certificate");
+// return loader.getClientCertificate();
+// }
+
@Override
public boolean isConnected() {
return client != null && isConnected;
@@ -545,7 +570,13 @@ public class OpcuaTcpPlcConnection extends BaseOpcuaPlcConnection {
}
private SecurityPolicy getSecurityPolicy() {
- return SecurityPolicy.None;
+ if (this.securityPolicy == null) {
+ logger.debug("No Security Policy given, using default NONE");
+ return SecurityPolicy.None;
+ } else {
+ logger.debug("Using given SecurityPolicy {}", this.securityPolicy);
+ return this.securityPolicy;
+ }
}
private IdentityProvider getIdentityProvider() {
@@ -553,18 +584,10 @@ public class OpcuaTcpPlcConnection extends BaseOpcuaPlcConnection {
if (this.password == null) {
throw new PlcRuntimeException("Username given, but no password. Please Add password by providing &password=");
}
- logger.info("Username {} is given and password {}, using Basic Auth now", this.username, this.password);
+ logger.debug("Username {} is given and password {}, using Basic Auth now", this.username, this.password);
return new UsernameProvider(this.username, this.password);
-// } else if (this.certFile != null) {
-// logger.info("Using Certificate Path {}", this.certFile);
-// try {
-// final X509Certificate x509Certificate = CertificateUtil.decodeCertificate(Files.readAllBytes(new File(this.certFile).toPath()));
-// return new X509IdentityProvider(x509Certificate);
-// } catch (UaException | IOException e) {
-// throw new PlcRuntimeException("Unable to load or decode Cert with path " + this.certFile);
-// }
} else {
- logger.info("No username / password is given, using anonymous access");
+ logger.debug("No username / password is given, using anonymous access");
return new AnonymousProvider();
}
}