Skip to content

Commit

Permalink
refactor(plc4j/opcua): slight cleanup and qc fixing
Browse files Browse the repository at this point in the history
  • Loading branch information
sruehl committed Jul 31, 2023
1 parent fb84689 commit c9efef3
Show file tree
Hide file tree
Showing 11 changed files with 288 additions and 265 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,41 +18,39 @@
*/
package org.apache.plc4x.java.opcua;

import io.netty.buffer.ByteBuf;
import org.apache.plc4x.java.api.PlcConnection;
import org.apache.plc4x.java.api.exceptions.PlcConnectionException;
import org.apache.plc4x.java.opcua.tag.OpcuaTag;
import org.apache.plc4x.java.opcua.tag.OpcuaPlcTagHandler;
import org.apache.plc4x.java.opcua.config.OpcuaConfiguration;
import org.apache.plc4x.java.opcua.optimizer.OpcuaOptimizer;
import org.apache.plc4x.java.opcua.protocol.*;
import org.apache.plc4x.java.opcua.config.*;
import org.apache.plc4x.java.opcua.readwrite.*;
import org.apache.plc4x.java.opcua.protocol.OpcuaProtocolLogic;
import org.apache.plc4x.java.opcua.readwrite.OpcuaAPU;
import org.apache.plc4x.java.opcua.tag.OpcuaPlcTagHandler;
import org.apache.plc4x.java.opcua.tag.OpcuaTag;
import org.apache.plc4x.java.spi.configuration.Configuration;
import org.apache.plc4x.java.spi.configuration.ConfigurationFactory;
import org.apache.plc4x.java.spi.connection.*;
import org.apache.plc4x.java.spi.transport.Transport;
import org.apache.plc4x.java.spi.values.PlcValueHandler;
import org.apache.plc4x.java.spi.configuration.Configuration;
import org.apache.plc4x.java.spi.connection.GeneratedDriverBase;
import io.netty.buffer.ByteBuf;

import java.util.ServiceLoader;
import java.util.function.ToIntFunction;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import java.util.function.ToIntFunction;

import static org.apache.plc4x.java.spi.configuration.ConfigurationFactory.configure;

public class OpcuaPlcDriver extends GeneratedDriverBase<OpcuaAPU> {

public static final Pattern INET_ADDRESS_PATTERN = Pattern.compile("(:(?<transportCode>[a-z0-9]*))?://" +
"(?<transportHost>[\\w.-]+)(:" +
"(?<transportPort>\\d*))?");
"(?<transportHost>[\\w.-]+)(:" +
"(?<transportPort>\\d*))?");

public static final Pattern URI_PATTERN = Pattern.compile("^(?<protocolCode>opcua)" +
INET_ADDRESS_PATTERN +
"(?<transportEndpoint>[\\w/=]*)[\\?]?" +
"(?<paramString>([^\\=]+\\=[^\\=&]+[&]?)*)"
);
INET_ADDRESS_PATTERN +
"(?<transportEndpoint>[\\w/=]*)[\\?]?" +
"(?<paramString>([^\\=]+\\=[^\\=&]+[&]?)*)"
);

private boolean isEncrypted;

Expand All @@ -76,11 +74,6 @@ protected String getDefaultTransport() {
return "tcp";
}

@Override
protected boolean awaitSetupComplete() {
return true;
}

@Override
protected boolean awaitDiscoverComplete() {
return isEncrypted;
Expand All @@ -101,11 +94,6 @@ protected boolean canSubscribe() {
return true;
}

@Override
protected boolean canBrowse() {
return false;
}

@Override
protected OpcuaOptimizer getOptimizer() {
return new OpcuaOptimizer();
Expand Down Expand Up @@ -152,7 +140,7 @@ public PlcConnection getConnection(String connectionString) throws PlcConnection
final String paramString = matcher.group("paramString");

// Check if the protocol code matches this driver.
if(!protocolCode.equals(getProtocolCode())) {
if (!protocolCode.equals(getProtocolCode())) {
// Actually this shouldn't happen as the DriverManager should have not used this driver in the first place.
throw new PlcConnectionException(
"This driver is not suited to handle this connection string");
Expand All @@ -161,7 +149,7 @@ public PlcConnection getConnection(String connectionString) throws PlcConnection
// Create the configuration object.
OpcuaConfiguration configuration = (OpcuaConfiguration) new ConfigurationFactory().createConfiguration(
getConfigurationType(), paramString);
if(configuration == null) {
if (configuration == null) {
throw new PlcConnectionException("Unsupported configuration");
}
configuration.setTransportCode(transportCode);
Expand All @@ -175,12 +163,12 @@ public PlcConnection getConnection(String connectionString) throws PlcConnection
ServiceLoader<Transport> transportLoader = ServiceLoader.load(
Transport.class, Thread.currentThread().getContextClassLoader());
for (Transport curTransport : transportLoader) {
if(curTransport.getTransportCode().equals(transportCode)) {
if (curTransport.getTransportCode().equals(transportCode)) {
transport = curTransport;
break;
}
}
if(transport == null) {
if (transport == null) {
throw new PlcConnectionException("Unsupported transport " + transportCode);
}

Expand All @@ -189,7 +177,7 @@ public PlcConnection getConnection(String connectionString) throws PlcConnection

// Create an instance of the communication channel which the driver should use.
ChannelFactory channelFactory = transport.createChannelFactory(transportHost + ":" + transportPort);
if(channelFactory == null) {
if (channelFactory == null) {
throw new PlcConnectionException("Unable to get channel factory from url " + transportHost + ":" + transportPort);
}
configure(configuration, channelFactory);
Expand All @@ -199,13 +187,13 @@ public PlcConnection getConnection(String connectionString) throws PlcConnection

// Make the "await setup complete" overridable via system property.
boolean awaitSetupComplete = awaitSetupComplete();
if(System.getProperty(PROPERTY_PLC4X_FORCE_AWAIT_SETUP_COMPLETE) != null) {
if (System.getProperty(PROPERTY_PLC4X_FORCE_AWAIT_SETUP_COMPLETE) != null) {
awaitSetupComplete = Boolean.parseBoolean(System.getProperty(PROPERTY_PLC4X_FORCE_AWAIT_SETUP_COMPLETE));
}

// Make the "await disconnect complete" overridable via system property.
boolean awaitDisconnectComplete = awaitDisconnectComplete();
if(System.getProperty(PROPERTY_PLC4X_FORCE_AWAIT_DISCONNECT_COMPLETE) != null) {
if (System.getProperty(PROPERTY_PLC4X_FORCE_AWAIT_DISCONNECT_COMPLETE) != null) {
awaitDisconnectComplete = Boolean.parseBoolean(System.getProperty(PROPERTY_PLC4X_FORCE_AWAIT_DISCONNECT_COMPLETE));
}

Expand All @@ -221,7 +209,7 @@ public PlcConnection getConnection(String connectionString) throws PlcConnection

// Make the "await disconnect complete" overridable via system property.
boolean awaitDiscoverComplete = awaitDiscoverComplete();
if(System.getProperty(PROPERTY_PLC4X_FORCE_AWAIT_DISCOVER_COMPLETE) != null) {
if (System.getProperty(PROPERTY_PLC4X_FORCE_AWAIT_DISCOVER_COMPLETE) != null) {
awaitDiscoverComplete = Boolean.parseBoolean(System.getProperty(PROPERTY_PLC4X_FORCE_AWAIT_DISCOVER_COMPLETE));
}

Expand All @@ -239,7 +227,9 @@ public PlcConnection getConnection(String connectionString) throws PlcConnection
null);
}

/** Estimate the Length of a Packet */
/**
* Estimate the Length of a Packet
*/
public static class ByteLengthEstimator implements ToIntFunction<ByteBuf> {
@Override
public int applyAsInt(ByteBuf byteBuf) {
Expand All @@ -251,7 +241,7 @@ public int applyAsInt(ByteBuf byteBuf) {
}

@Override
public OpcuaTag prepareTag(String tagAddress){
public OpcuaTag prepareTag(String tagAddress) {
return OpcuaTag.of(tagAddress);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,10 @@
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.opcua.readwrite.PascalByteString;
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.bouncycastle.jce.provider.BouncyCastleProvider;
Expand All @@ -36,11 +34,11 @@
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.KeyPair;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.Security;
import java.security.cert.X509Certificate;

public class OpcuaConfiguration implements Configuration, TcpTransportConfiguration {
Expand Down Expand Up @@ -123,7 +121,9 @@ public CertificateKeyPair getCertificateKeyPair() {
return ckp;
}

public boolean isEncrypted() { return isEncrypted; }
public boolean isEncrypted() {
return isEncrypted;
}

public void setDiscovery(boolean discovery) {
this.discovery = discovery;
Expand Down Expand Up @@ -153,7 +153,9 @@ public void setKeyStorePassword(String keyStorePassword) {
this.keyStorePassword = keyStorePassword;
}

public void setThumbprint(PascalByteString thumbprint) { this.thumbprint = thumbprint; }
public void setThumbprint(PascalByteString thumbprint) {
this.thumbprint = thumbprint;
}

public String getTransportCode() {
return code;
Expand Down Expand Up @@ -195,13 +197,15 @@ public void setEndpoint(String endpoint) {
this.endpoint = endpoint;
}

public void setTransportEndpoint(String transportEndpoint) { this.transportEndpoint = transportEndpoint; }
public void setTransportEndpoint(String transportEndpoint) {
this.transportEndpoint = transportEndpoint;
}

public void openKeyStore() throws Exception {
this.isEncrypted = true;
File securityTempDir = new File(certDirectory, "security");
if (!securityTempDir.exists() && !securityTempDir.mkdirs()) {
throw new PlcConnectionException("Unable to create directory please confirm folder permissions on " + certDirectory);
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();
Expand All @@ -211,25 +215,26 @@ public void openKeyStore() throws Exception {
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.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));
ckp = new CertificateKeyPair(kp, (X509Certificate) keyStore.getCertificate(alias));
}
}

@Override
public String toString() {
return "Configuration{" +
'}';
return "Configuration{" + '}';
}

public void setSenderCertificate(byte[] certificate) { this.senderCertificate = certificate; }
public void setSenderCertificate(byte[] certificate) {
this.senderCertificate = certificate;
}

}

Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ public static CertificateKeyPair generateCertificate() {
kpg = KeyPairGenerator.getInstance("RSA");
} catch (NoSuchAlgorithmException e) {
LOGGER.error("Security Algorithim is unsupported for certificate");
return null;
}
kpg.initialize(2048);
KeyPair caKeys = kpg.generateKeyPair();
Expand All @@ -73,7 +74,7 @@ public static CertificateKeyPair generateCertificate() {
final Calendar calender = Calendar.getInstance();
calender.add(Calendar.DATE, -1);
Date startDate = calender.getTime();
calender.add(Calendar.DATE, 365*25);
calender.add(Calendar.DATE, 365 * 25);
Date expiryDate = calender.getTime();

KeyPairGenerator generator = null;
Expand All @@ -94,13 +95,13 @@ public static CertificateKeyPair generateCertificate() {
Locale.ENGLISH,
nameBuilder.build(),
subjectPublicKeyInfo
);
);

GeneralName[] gnArray = new GeneralName[] {new GeneralName(GeneralName.dNSName, InetAddress.getLocalHost().getHostName()), new GeneralName(GeneralName.uniformResourceIdentifier, APPURI)};
GeneralName[] gnArray = new GeneralName[]{new GeneralName(GeneralName.dNSName, InetAddress.getLocalHost().getHostName()), new GeneralName(GeneralName.uniformResourceIdentifier, APPURI)};

certificateBuilder.addExtension(Extension.authorityKeyIdentifier, false, new JcaX509ExtensionUtils().createAuthorityKeyIdentifier(keyPair.getPublic()));
certificateBuilder.addExtension(Extension.extendedKeyUsage, false, new ExtendedKeyUsage(new KeyPurposeId[]{KeyPurposeId.id_kp_clientAuth, KeyPurposeId.id_kp_serverAuth}));
certificateBuilder.addExtension(Extension.keyUsage,false, new KeyUsage(KeyUsage.dataEncipherment | KeyUsage.digitalSignature | KeyUsage.keyAgreement | KeyUsage.keyCertSign | KeyUsage.keyEncipherment | KeyUsage.nonRepudiation));
certificateBuilder.addExtension(Extension.keyUsage, false, new KeyUsage(KeyUsage.dataEncipherment | KeyUsage.digitalSignature | KeyUsage.keyAgreement | KeyUsage.keyCertSign | KeyUsage.keyEncipherment | KeyUsage.nonRepudiation));
certificateBuilder.addExtension(Extension.basicConstraints, false, new BasicConstraints(true));

GeneralNames subjectAltNames = GeneralNames.getInstance(new DERSequence(gnArray));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,22 @@ public class CertificateKeyPair {
private final X509Certificate certificate;
private final byte[] thumbprint;

public CertificateKeyPair(KeyPair keyPair, X509Certificate certificate) throws Exception{
public CertificateKeyPair(KeyPair keyPair, X509Certificate certificate) throws Exception {
this.keyPair = keyPair;
this.certificate = certificate;
MessageDigest messageDigest = MessageDigest.getInstance("SHA-1");
this.thumbprint = messageDigest.digest(this.certificate.getEncoded());
}

public KeyPair getKeyPair() { return keyPair; }
public KeyPair getKeyPair() {
return keyPair;
}

public X509Certificate getCertificate() { return certificate; }
public X509Certificate getCertificate() {
return certificate;
}

public byte[] getThumbPrint() { return thumbprint; }
public byte[] getThumbPrint() {
return thumbprint;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,10 @@
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.ByteArrayInputStream;
import java.security.*;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Security;
import java.security.Signature;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;

Expand All @@ -42,6 +45,8 @@ public class EncryptionHandler {

private static final Logger LOGGER = LoggerFactory.getLogger(OpcuaProtocolLogic.class);

private static int PREENCRYPTED_BLOCK_LENGTH = 190;

static {
// Required for SecurityPolicy.Aes256_Sha256_RsaPss
Security.addProvider(new BouncyCastleProvider());
Expand Down Expand Up @@ -70,7 +75,6 @@ public void setServerCertificate(X509Certificate serverCertificate) {
}

public ReadBuffer encodeMessage(MessagePDU pdu, byte[] message) {
int PREENCRYPTED_BLOCK_LENGTH = 190;
int unencryptedLength = pdu.getLengthInBytes();
int openRequestLength = message.length;
int positionFirstBlock = unencryptedLength - openRequestLength - 8;
Expand Down Expand Up @@ -101,7 +105,7 @@ public ReadBuffer encodeMessage(MessagePDU pdu, byte[] message) {
}
buf.setPos(positionFirstBlock);
encryptBlock(buf, getBytes(buf.getBytes(), positionFirstBlock, positionFirstBlock + preEncryptedLength));
return new ReadBufferByteBased(buf.getData(), ByteOrder.LITTLE_ENDIAN);
return new ReadBufferByteBased(buf.getBytes(), ByteOrder.LITTLE_ENDIAN);
} catch (SerializationException e) {
throw new PlcRuntimeException("Unable to parse apu prior to encrypting");
}
Expand Down Expand Up @@ -170,8 +174,7 @@ public boolean checkSignature(byte[] data) {
signature.update(data);
return signature.verify(data, 0, data.length - 256);
} catch (Exception e) {
e.printStackTrace();
LOGGER.error("Unable to sign Data");
LOGGER.error("Unable to sign Data", e);
return false;
}
}
Expand Down Expand Up @@ -199,8 +202,7 @@ public void encryptBlock(WriteBuffer buf, byte[] data) {
}
}
} catch (Exception e) {
LOGGER.error("Unable to encrypt Data");
e.printStackTrace();
LOGGER.error("Unable to encrypt Data", e);
}
}

Expand Down Expand Up @@ -238,8 +240,7 @@ public byte[] sign(byte[] data) {
LOGGER.info("----------------Signature Length{}", ss.length);
return ss;
} catch (Exception e) {
e.printStackTrace();
LOGGER.error("Unable to sign Data");
LOGGER.error("Unable to sign Data", e);
return null;
}
}
Expand Down
Loading

0 comments on commit c9efef3

Please sign in to comment.