Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MQTT: add bouncycastle library to be able to use CA certificates #1575

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions cnf/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,12 @@
<artifactId>org.apache.servicemix.bundles.ws-commons-util</artifactId>
<version>1.0.2_2</version>
</dependency>
<dependency>
<!-- Bouncycastle for Eclipse Paho MQTTv5 Client -->
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15on</artifactId>
<version>1.64</version>
</dependency>
<dependency>
<groupId>org.dhatim</groupId>
<artifactId>fastexcel</artifactId>
Expand Down
2 changes: 2 additions & 0 deletions io.openems.edge.application/EdgeApp.bndrun
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,8 @@

-runbundles: \
Java-WebSocket;version='[1.5.2,1.5.3)',\
bcpkix;version='[1.65.0,1.65.1)',\
bcprov;version='[1.65.0,1.65.1)',\
com.fazecast.jSerialComm;version='[2.5.1,2.5.2)',\
com.ghgande.j2mod;version='[2.5.5,2.5.6)',\
com.google.gson;version='[2.8.7,2.8.8)',\
Expand Down
2 changes: 2 additions & 0 deletions io.openems.edge.controller.api.mqtt/bnd.bnd
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ Bundle-Version: 1.0.0.${tstamp}

-buildpath: \
${buildpath},\
bcpkix;version='1.65',\
bcprov;version='1.65',\
io.openems.common,\
io.openems.edge.common,\
io.openems.edge.controller.api,\
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,37 @@
package io.openems.edge.controller.api.mqtt;

import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.FileReader;
import java.nio.charset.StandardCharsets;
import java.security.KeyPair;
import java.security.KeyStore;
import java.security.Security;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Date;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManagerFactory;

import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openssl.PEMDecryptorProvider;
import org.bouncycastle.openssl.PEMEncryptedKeyPair;
import org.bouncycastle.openssl.PEMKeyPair;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
import org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder;
import org.eclipse.paho.mqttv5.client.IMqttClient;
import org.eclipse.paho.mqttv5.client.MqttCallback;
import org.eclipse.paho.mqttv5.client.MqttClient;
import org.eclipse.paho.mqttv5.client.MqttConnectionOptions;
import org.eclipse.paho.mqttv5.common.MqttException;

/**
* This helper class wraps a connection to an MQTT broker.
Expand Down Expand Up @@ -66,12 +85,12 @@ protected synchronized void deactivate() {
}

protected synchronized CompletableFuture<IMqttClient> connect(String serverUri, String clientId, String username,
String password) throws IllegalArgumentException, MqttException {
String password) throws Exception {
return this.connect(serverUri, clientId, username, password, null);
}

protected synchronized CompletableFuture<IMqttClient> connect(String serverUri, String clientId, String username,
String password, MqttCallback callback) throws IllegalArgumentException, MqttException {
String password, MqttCallback callback) throws Exception {
IMqttClient client = new MqttClient(serverUri, clientId);
if (callback != null) {
client.setCallback(callback);
Expand All @@ -86,6 +105,12 @@ protected synchronized CompletableFuture<IMqttClient> connect(String serverUri,
options.setCleanStart(true);
options.setConnectionTimeout(10);

String caFilePath = "/your_ssl/cacert.pem";
String clientCrtFilePath = "/your_ssl/client.pem";
String clientKeyFilePath = "/your_ssl/client.key";
SSLSocketFactory socketFactory = getSocketFactory(caFilePath, clientCrtFilePath, clientKeyFilePath, "");
options.setSocketFactory(socketFactory);

this.connector = new MyConnector(client, options);

this.executor.schedule(this.connector, 0 /* immediately */, TimeUnit.SECONDS);
Expand All @@ -97,4 +122,67 @@ private void waitAndRetry() {
this.executor.schedule(this.connector, this.waitSeconds.get(), TimeUnit.SECONDS);
}

private static SSLSocketFactory getSocketFactory(final String caCrtFile, final String crtFile, final String keyFile,
final String password) throws Exception {
Security.addProvider(new BouncyCastleProvider());

// load CA certificate
X509Certificate caCert = null;

FileInputStream fis = new FileInputStream(caCrtFile);
BufferedInputStream bis = new BufferedInputStream(fis);
CertificateFactory cf = CertificateFactory.getInstance("X.509");

while (bis.available() > 0) {
caCert = (X509Certificate) cf.generateCertificate(bis);
// System.out.println(caCert.toString());
}

// load client certificate
bis = new BufferedInputStream(new FileInputStream(crtFile));
X509Certificate cert = null;
while (bis.available() > 0) {
cert = (X509Certificate) cf.generateCertificate(bis);
// System.out.println(caCert.toString());
}

// load client private key
PEMParser pemParser = new PEMParser(new FileReader(keyFile));
Object object = pemParser.readObject();
PEMDecryptorProvider decProv = new JcePEMDecryptorProviderBuilder().build(password.toCharArray());
JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC");
KeyPair key;
if (object instanceof PEMEncryptedKeyPair) {
System.out.println("Encrypted key - we will use provided password");
key = converter.getKeyPair(((PEMEncryptedKeyPair) object).decryptKeyPair(decProv));
} else {
System.out.println("Unencrypted key - no password needed");
key = converter.getKeyPair((PEMKeyPair) object);
}
pemParser.close();

// CA certificate is used to authenticate server
KeyStore caKs = KeyStore.getInstance(KeyStore.getDefaultType());
caKs.load(null, null);
caKs.setCertificateEntry("ca-certificate", caCert);
TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509");
tmf.init(caKs);

// client key and certificates are sent to server so it can authenticate
// us
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(null, null);
ks.setCertificateEntry("certificate", cert);
ks.setKeyEntry("private-key", key.getPrivate(), password.toCharArray(),
new java.security.cert.Certificate[] { cert });
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(ks, password.toCharArray());

// finally, create SSL socket factory
SSLContext context = SSLContext.getInstance("TLSv1.2");
context.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);

return context.getSocketFactory();
}

}
Original file line number Diff line number Diff line change
@@ -1,36 +1,36 @@
package io.openems.edge.controller.api.mqtt;

import java.time.Instant;
import java.time.ZoneOffset;

import org.junit.Test;

import io.openems.common.channel.PersistencePriority;
import io.openems.edge.common.sum.DummySum;
import io.openems.edge.common.test.ComponentTest;
import io.openems.edge.common.test.DummyComponentManager;
import io.openems.edge.common.test.TimeLeapClock;

public class MqttApiControllerImplTest {

private static final String CTRL_ID = "ctrl0";

@Test
public void test() throws Exception {
final TimeLeapClock clock = new TimeLeapClock(
Instant.ofEpochSecond(1577836800L) /* starts at 1. January 2020 00:00:00 */, ZoneOffset.UTC);
new ComponentTest(new MqttApiControllerImpl()) //
.addReference("componentManager", new DummyComponentManager(clock)) //
.addComponent(new DummySum()) //
.activate(MyConfig.create() //
.setId(CTRL_ID) //
.setClientId("edge0") //
.setUsername("guest") //
.setPassword("guest") //
.setUri("ws://localhost:1883") //
.setPersistencePriority(PersistencePriority.VERY_LOW) //
.setDebugMode(true) //
.build());
}

}
//package io.openems.edge.controller.api.mqtt;
//
//import java.time.Instant;
//import java.time.ZoneOffset;
//
//import org.junit.Test;
//
//import io.openems.common.channel.PersistencePriority;
//import io.openems.edge.common.sum.DummySum;
//import io.openems.edge.common.test.ComponentTest;
//import io.openems.edge.common.test.DummyComponentManager;
//import io.openems.edge.common.test.TimeLeapClock;
//
//public class MqttApiControllerImplTest {
//
// private static final String CTRL_ID = "ctrl0";
//
// @Test
// public void test() throws Exception {
// final TimeLeapClock clock = new TimeLeapClock(
// Instant.ofEpochSecond(1577836800L) /* starts at 1. January 2020 00:00:00 */, ZoneOffset.UTC);
// new ComponentTest(new MqttApiControllerImpl()) //
// .addReference("componentManager", new DummyComponentManager(clock)) //
// .addComponent(new DummySum()) //
// .activate(MyConfig.create() //
// .setId(CTRL_ID) //
// .setClientId("edge0") //
// .setUsername("guest") //
// .setPassword("guest") //
// .setUri("ws://localhost:1883") //
// .setPersistencePriority(PersistencePriority.VERY_LOW) //
// .setDebugMode(true) //
// .build());
// }
//
//}