You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@plc4x.apache.org by hu...@apache.org on 2021/01/22 11:58:50 UTC
[plc4x] branch feature/native_opua_client updated: Add
Certificategenerator and connection string param for Security
This is an automated email from the ASF dual-hosted git repository.
hutcheb pushed a commit to branch feature/native_opua_client
in repository https://gitbox.apache.org/repos/asf/plc4x.git
The following commit(s) were added to refs/heads/feature/native_opua_client by this push:
new 11b9d51 Add Certificategenerator and connection string param for Security
11b9d51 is described below
commit 11b9d51ec6824f8a83ff6f892a9ec72f9d1c6164
Author: hutcheb <be...@gmail.com>
AuthorDate: Fri Jan 22 06:57:55 2021 -0500
Add Certificategenerator and connection string param for Security
---
plc4j/drivers/opcua/pom.xml | 17 ++-
.../apache/plc4x/java/opcua/OpcuaPlcDriver.java | 2 -
.../java/opcua/config/OpcuaConfiguration.java | 85 +++++++++++++-
.../opcua/connection/BaseOpcuaPlcConnection.java | 103 -----------------
.../java/opcua/context/CertificateGenerator.java | 124 +++++++++++++++++++++
.../java/opcua/context/CertificateKeyPair.java | 38 +++++++
.../java/opcua/protocol/OpcuaProtocolLogic.java | 2 +-
7 files changed, 257 insertions(+), 114 deletions(-)
diff --git a/plc4j/drivers/opcua/pom.xml b/plc4j/drivers/opcua/pom.xml
index aac1c0c..61267b7 100644
--- a/plc4j/drivers/opcua/pom.xml
+++ b/plc4j/drivers/opcua/pom.xml
@@ -107,6 +107,8 @@
<usedDependency>org.apache.plc4x:plc4j-transport-raw-socket</usedDependency>
<usedDependency>org.apache.plc4x:plc4x-build-utils-language-java</usedDependency>
<usedDependency>org.apache.plc4x:plc4x-protocols-opcua</usedDependency>
+ <usedDependency>org.bouncycastle:bcpkix-jdk15on</usedDependency>
+ <usedDependency>org.bouncycastle:bcprov-jdk15on</usedDependency>
</usedDependencies>
</configuration>
</plugin>
@@ -206,6 +208,18 @@
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</dependency>
+
+ <dependency>
+ <groupId>org.bouncycastle</groupId>
+ <artifactId>bcpkix-jdk15on</artifactId>
+ <scope>compile</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.bouncycastle</groupId>
+ <artifactId>bcprov-jdk15on</artifactId>
+ <scope>compile</scope>
+ </dependency>
+
</dependencies>
<dependencyManagement>
@@ -220,7 +234,7 @@
<groupId>org.eclipse.milo</groupId>
<artifactId>stack-core</artifactId>
<version>${milo.version}</version>
- <scope>scope</scope>
+ <scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.milo</groupId>
@@ -235,7 +249,6 @@
<scope>test</scope>
</dependency>
-
</dependencies>
</dependencyManagement>
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 34ac6e8..1a391f5 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
@@ -136,8 +136,6 @@ public class OpcuaPlcDriver extends GeneratedDriverBase<OpcuaAPU> {
.build();
}
-
-
@Override
public PlcConnection getConnection(String connectionString) throws PlcConnectionException {
// Split up the connection string into it's individual segments.
diff --git a/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/config/OpcuaConfiguration.java b/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/config/OpcuaConfiguration.java
index 94ae1d5..c9ff79f 100644
--- a/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/config/OpcuaConfiguration.java
+++ b/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/config/OpcuaConfiguration.java
@@ -19,20 +19,51 @@ under the License.
package org.apache.plc4x.java.opcua.config;
+import org.apache.plc4x.java.api.exceptions.PlcConnectionException;
+import org.apache.plc4x.java.opcua.context.CertificateGenerator;
+import org.apache.plc4x.java.opcua.context.CertificateKeyPair;
+import org.apache.plc4x.java.opcua.protocol.OpcuaProtocolLogic;
import org.apache.plc4x.java.spi.configuration.Configuration;
import org.apache.plc4x.java.spi.configuration.annotations.ConfigurationParameter;
import org.apache.plc4x.java.spi.configuration.annotations.defaults.BooleanDefaultValue;
import org.apache.plc4x.java.spi.configuration.annotations.defaults.IntDefaultValue;
+import org.apache.plc4x.java.spi.configuration.annotations.defaults.StringDefaultValue;
import org.apache.plc4x.java.transport.tcp.TcpTransportConfiguration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.file.FileSystems;
+import java.nio.file.Path;
+import java.security.*;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
public class OpcuaConfiguration implements Configuration, TcpTransportConfiguration {
+ private static final Logger LOGGER = LoggerFactory.getLogger(OpcuaConfiguration.class);
+
private String code;
private String host;
private String port;
private String endpoint;
private String params;
+ public OpcuaConfiguration() {
+ if (!(securityPolicy == "None")) {
+ try {
+ if (!(keyStoreFile == null) & !(certDirectory == null) & !(keyStorePassword == null)) {
+ openKeyStore();
+ }
+ } catch (Exception e) {
+ LOGGER.info("Unable to open keystore, please confirm you have the correct permissions");
+ }
+ }
+ }
+
@ConfigurationParameter("discovery")
@BooleanDefaultValue(true)
private boolean discovery;
@@ -43,15 +74,21 @@ public class OpcuaConfiguration implements Configuration, TcpTransportConfigurat
@ConfigurationParameter("password")
private String password;
- @ConfigurationParameter("certFile")
- private String certFile;
-
@ConfigurationParameter("securityPolicy")
+ @StringDefaultValue("None")
private String securityPolicy;
@ConfigurationParameter("keyStoreFile")
private String keyStoreFile;
+ @ConfigurationParameter("certDirectory")
+ private String certDirectory;
+
+ @ConfigurationParameter("keyStorePassword")
+ private String keyStorePassword;
+
+ private CertificateKeyPair ckp;
+
public boolean isDiscovery() {
return discovery;
}
@@ -64,8 +101,8 @@ public class OpcuaConfiguration implements Configuration, TcpTransportConfigurat
return password;
}
- public String getCertFile() {
- return certFile;
+ public String getCertDirectory() {
+ return certDirectory;
}
public String getSecurityPolicy() {
@@ -76,6 +113,14 @@ public class OpcuaConfiguration implements Configuration, TcpTransportConfigurat
return keyStoreFile;
}
+ public String getKeyStorePassword() {
+ return keyStorePassword;
+ }
+
+ public CertificateKeyPair getCertificateKeyPair() {
+ return ckp;
+ }
+
public void setDiscovery(boolean discovery) {
this.discovery = discovery;
}
@@ -89,7 +134,7 @@ public class OpcuaConfiguration implements Configuration, TcpTransportConfigurat
}
public void setCertFile(String certFile) {
- this.certFile = certFile;
+ this.certDirectory = certFile;
}
public void setSecurityPolicy(String securityPolicy) {
@@ -100,6 +145,10 @@ public class OpcuaConfiguration implements Configuration, TcpTransportConfigurat
this.keyStoreFile = keyStoreFile;
}
+ public void setKeyStorePassword(String keyStorePassword) {
+ this.keyStorePassword = keyStorePassword;
+ }
+
public String getTransportCode() {
return code;
}
@@ -132,6 +181,30 @@ public class OpcuaConfiguration implements Configuration, TcpTransportConfigurat
this.endpoint = endpoint;
}
+ private void openKeyStore() throws KeyStoreException, PlcConnectionException, CertificateException, NoSuchAlgorithmException, IOException, UnrecoverableKeyException {
+ File securityTempDir = new File(certDirectory, "security");
+ if (!securityTempDir.exists() && !securityTempDir.mkdirs()) {
+ throw new PlcConnectionException("Unable to create directory please confirm folder permissions on " + certDirectory);
+ }
+ KeyStore keyStore = KeyStore.getInstance("PKCS12");
+ File serverKeyStore = securityTempDir.toPath().resolve(keyStoreFile).toFile();
+
+ File pkiDir = FileSystems.getDefault().getPath(certDirectory).resolve("pki").toFile();
+ if (!serverKeyStore.exists()) {
+ ckp = CertificateGenerator.generateCertificate();
+ LOGGER.info("Creating new KeyStore at {}", serverKeyStore);
+ keyStore.load(null, keyStorePassword.toCharArray());
+ keyStore.setKeyEntry("plc4x-certificate-alias", ckp.getKeyPair().getPrivate(), keyStorePassword.toCharArray(), new X509Certificate[] { ckp.getCertificate() });
+ keyStore.store(new FileOutputStream(serverKeyStore), keyStorePassword.toCharArray());
+ } else {
+ LOGGER.info("Loading KeyStore at {}", serverKeyStore);
+ keyStore.load(new FileInputStream(serverKeyStore), keyStorePassword.toCharArray());
+ String alias = keyStore.aliases().nextElement();
+ KeyPair kp = new KeyPair(keyStore.getCertificate(alias).getPublicKey(),
+ (PrivateKey) keyStore.getKey(alias, keyStorePassword.toCharArray()));
+ ckp = new CertificateKeyPair(kp,(X509Certificate) keyStore.getCertificate(alias));
+ }
+ }
@Override
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
deleted file mode 100644
index feedc43..0000000
--- a/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/connection/BaseOpcuaPlcConnection.java
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- Licensed to the Apache Software Foundation (ASF) under one
- or more contributor license agreements. See the NOTICE file
- distributed with this work for additional information
- regarding copyright ownership. The ASF licenses this file
- to you under the Apache License, Version 2.0 (the
- "License"); you may not use this file except in compliance
- with the License. You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing,
- software distributed under the License is distributed on an
- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- KIND, either express or implied. See the License for the
- specific language governing permissions and limitations
- under the License.
-*/
-package org.apache.plc4x.java.opcua.connection;
-
-import org.apache.commons.lang3.StringUtils;
-import org.apache.plc4x.java.api.messages.PlcReadRequest;
-import org.apache.plc4x.java.api.messages.PlcSubscriptionRequest;
-import org.apache.plc4x.java.api.messages.PlcUnsubscriptionRequest;
-import org.apache.plc4x.java.api.messages.PlcWriteRequest;
-import org.apache.plc4x.java.opcua.protocol.OpcuaPlcFieldHandler;
-import org.apache.plc4x.java.spi.connection.AbstractPlcConnection;
-import org.apache.plc4x.java.spi.messages.*;
-import org.apache.plc4x.java.spi.values.IEC61131ValueHandler;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- */
-public abstract class BaseOpcuaPlcConnection extends AbstractPlcConnection implements PlcReader, PlcWriter, PlcSubscriber {
-
- private static final Logger logger = LoggerFactory.getLogger(BaseOpcuaPlcConnection.class);
- protected boolean skipDiscovery = false;
-
- /**
- * @param params
- */
- BaseOpcuaPlcConnection(String params) {
-
- if (!StringUtils.isEmpty(params)) {
- for (String param : params.split("&")) {
- String[] paramElements = param.split("=");
- String paramName = paramElements[0];
- if (paramElements.length == 2) {
- String paramValue = paramElements[1];
- switch (paramName) {
- case "discovery":
- skipDiscovery = !Boolean.valueOf(paramValue);
- break;
- default:
- logger.debug("Unknown parameter {} with value {}", paramName, paramValue);
- }
- } else {
- logger.debug("Unknown no-value parameter {}", paramName);
- }
- }
- }
- }
-
- @Override
- public boolean canRead() {
- return true;
- }
-
- @Override
- public boolean canWrite() {
- return true;
- }
-
- @Override
- public PlcReadRequest.Builder readRequestBuilder() {
- return new DefaultPlcReadRequest.Builder(this, new OpcuaPlcFieldHandler());
- }
-
- @Override
- public PlcWriteRequest.Builder writeRequestBuilder() {
- return new DefaultPlcWriteRequest.Builder(this, new OpcuaPlcFieldHandler(), new IEC61131ValueHandler());
- }
-
- @Override
- public boolean canSubscribe() {
- return true;
- }
-
- @Override
- public PlcSubscriptionRequest.Builder subscriptionRequestBuilder() {
- return new DefaultPlcSubscriptionRequest.Builder(this, new OpcuaPlcFieldHandler());
- }
-
- @Override
- public PlcUnsubscriptionRequest.Builder unsubscriptionRequestBuilder() {
- return new DefaultPlcUnsubscriptionRequest.Builder(this);
- }
-
- public boolean isSkipDiscovery() {
- return skipDiscovery;
- }
-}
diff --git a/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/context/CertificateGenerator.java b/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/context/CertificateGenerator.java
new file mode 100644
index 0000000..15d302e
--- /dev/null
+++ b/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/context/CertificateGenerator.java
@@ -0,0 +1,124 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.plc4x.java.opcua.context;
+
+import org.apache.commons.lang3.RandomUtils;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.x500.X500NameBuilder;
+import org.bouncycastle.asn1.x500.style.BCStyle;
+import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.asn1.x509.GeneralName;
+import org.bouncycastle.asn1.x509.GeneralNames;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.cert.X509v3CertificateBuilder;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.operator.ContentSigner;
+import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.math.BigInteger;
+import java.net.InetAddress;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.Locale;
+
+public class CertificateGenerator<PKCS10CertificateRequest> {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(CertificateGenerator.class);
+ private static final String APPURI = "urn:eclipse:milo:plc4x:server";
+
+ public static CertificateKeyPair generateCertificate() {
+ KeyPairGenerator kpg = null;
+ try {
+ kpg = KeyPairGenerator.getInstance("RSA");
+ } catch (NoSuchAlgorithmException e) {
+ LOGGER.error("Security Algorithim is unsupported for certificate");
+ }
+ kpg.initialize(2048);
+ KeyPair caKeys = kpg.generateKeyPair();
+ KeyPair userKeys = kpg.generateKeyPair();
+
+ X500NameBuilder nameBuilder = new X500NameBuilder();
+
+ nameBuilder.addRDN(BCStyle.CN, "Apache PLC4X Driver Client");
+ nameBuilder.addRDN(BCStyle.O, "Apache Software Foundation");
+ nameBuilder.addRDN(BCStyle.OU, "dev");
+ nameBuilder.addRDN(BCStyle.L, "");
+ nameBuilder.addRDN(BCStyle.ST, "DE");
+ nameBuilder.addRDN(BCStyle.C, "US");
+
+ BigInteger serial = new BigInteger(RandomUtils.nextBytes(40));
+
+ final Calendar calender = Calendar.getInstance();
+ calender.add(Calendar.DATE, -1);
+ Date startDate = calender.getTime();
+ calender.add(Calendar.DATE, 365*25);
+ Date expiryDate = calender.getTime();
+
+ KeyPairGenerator generator = null;
+ try {
+ generator = KeyPairGenerator.getInstance("RSA");
+ generator.initialize(2048, new SecureRandom());
+ KeyPair keyPair = generator.generateKeyPair();
+
+ SubjectPublicKeyInfo subjectPublicKeyInfo = SubjectPublicKeyInfo.getInstance(
+ keyPair.getPublic().getEncoded()
+ );
+
+ X509v3CertificateBuilder certificateBuilder = new X509v3CertificateBuilder(
+ nameBuilder.build(),
+ serial,
+ startDate,
+ expiryDate,
+ Locale.ENGLISH,
+ nameBuilder.build(),
+ subjectPublicKeyInfo
+ );
+
+ GeneralName[] gnArray = new GeneralName[] {new GeneralName(GeneralName.dNSName, InetAddress.getLocalHost().getHostName()), new GeneralName(GeneralName.uniformResourceIdentifier, APPURI)};
+
+
+ GeneralNames subjectAltNames = GeneralNames.getInstance(new DERSequence(gnArray));
+ certificateBuilder.addExtension(Extension.subjectAlternativeName, false, subjectAltNames);
+
+ ContentSigner sigGen = new JcaContentSignerBuilder("SHA1withRSA").setProvider("BC").build(keyPair.getPrivate());
+
+ X509CertificateHolder certificateHolder = certificateBuilder.build(sigGen);
+
+ JcaX509CertificateConverter certificateConvertor = new JcaX509CertificateConverter();
+ certificateConvertor.setProvider(new BouncyCastleProvider());
+
+ CertificateKeyPair ckp = new CertificateKeyPair(keyPair, certificateConvertor.getCertificate(certificateHolder));
+
+ return ckp;
+
+ } catch (Exception e) {
+ LOGGER.error("Security Algorithim is unsupported for certificate");
+ return null;
+ }
+ }
+}
diff --git a/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/context/CertificateKeyPair.java b/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/context/CertificateKeyPair.java
new file mode 100644
index 0000000..a70aa3b
--- /dev/null
+++ b/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/context/CertificateKeyPair.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.plc4x.java.opcua.context;
+
+import java.security.KeyPair;
+import java.security.cert.X509Certificate;
+
+public class CertificateKeyPair {
+
+ private final KeyPair keyPair;
+ private final X509Certificate certificate;
+
+ public CertificateKeyPair(KeyPair keyPair, X509Certificate certificate) {
+ this.keyPair = keyPair;
+ this.certificate = certificate;
+ }
+
+ public KeyPair getKeyPair() { return keyPair; }
+
+ public X509Certificate getCertificate() { return certificate; }
+}
diff --git a/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/protocol/OpcuaProtocolLogic.java b/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/protocol/OpcuaProtocolLogic.java
index cfe61c6..5515121 100644
--- a/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/protocol/OpcuaProtocolLogic.java
+++ b/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/protocol/OpcuaProtocolLogic.java
@@ -163,7 +163,7 @@ public class OpcuaProtocolLogic extends Plc4xProtocolBase<OpcuaAPU> implements H
this.discovery = configuration.isDiscovery();
this.username = configuration.getUsername();
this.password = configuration.getPassword();
- this.certFile = configuration.getCertFile();
+ this.certFile = configuration.getCertDirectory();
this.securityPolicy = configuration.getSecurityPolicy();
this.keyStoreFile = configuration.getKeyStoreFile();