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();