B4J Question MQTT with ca,cert & key file

madru

Active Member
Licensed User
Longtime User
Good morning,

I developed some software for a client in 2015 to fetch sensor data via MQTT and to do some analytics and graphical statistics with that data
The main program is witten in B4J and the MQTT communication is done in Python. My client asked for some changes and I tried (for the sake of simplicity) to remove the Python part to do everything in B4J but I failed. I have absolutely no idea how to load the necessary ca,key,cer file and pass those to paho in B4J.

can somebody point me into the right direction?

THX



this is how it is done in Python
B4X:
cafile = os.path.join(certdir, "root_ca_auth.cer")
certfile = os.path.join(certdir, "device_auth.crt")
keyfile = os.path.join(certdir, "device_auth.key")

mqttc = pahomqtt.Client(client_id=client_gen_id)
mqttc.on_connect = on_connect
mqttc.on_disconnect = on_disconnect
mqttc.on_message = on_message
mqttc.username_pw_set(cert_user, password=client_password)
mqttc.tls_set(cafile, certfile, keyfile)
mqttc.tls_insecure_set(True)
 

DonManfred

Expert
Licensed User
Longtime User
It should be too complicated
really should be? So it is complicated?

Edit to add: Erel has adapted his post ;-)
 
Last edited:
Upvote 0

madru

Active Member
Licensed User
Longtime User
Hi,

thx for the suggestion :)

I modified the Git code to get it work with the actual bouncycastle. (I hope)

I can compile it with javac without any problems, but I can't get it to work as inline code


any ideas?

THX

B4X:
B4J Version: 8.00
Java Version: 8
Parsing code.    (0.03s)
Building folders structure.    (0.37s)
Compiling code.    (0.07s)
Compiling layouts code.    (0.00s)
Organizing libraries.    (0.00s)
Compiling generated Java code.    Error
B4J line: 15
End Sub
javac 1.8.0_152
src\b4j\example\main.java:102: error: Illegal static declaration in inner class main.SslUtil
    static SSLSocketFactory getSocketFactory (final String caCrtFile, final String crtFile, final String keyFile,
                            ^
  modifier 'static' is only allowed in constant variable declarations
1 error

B4X:
#Region Project Attributes 
    #MainFormWidth: 600
    #MainFormHeight: 600 
#End Region

Sub Process_Globals
    Private fx As JFX
    Private MainForm As Form
End Sub

Sub AppStart (Form1 As Form, Args() As String)
    MainForm = Form1
    'MainForm.RootPane.LoadLayout("Layout1") 'Load the layout file.
    MainForm.Show
End Sub




'Return true to allow the default exceptions handler to handle the uncaught exception.
Sub Application_Error (Error As Exception, StackTrace As String) As Boolean
    Return True
End Sub

#If JAVA
import java.io.*;
import java.nio.file.*;
import java.security.*;
import java.security.cert.*;
import javax.net.ssl.*;
import org.bouncycastle.jce.provider.*;
import org.bouncycastle.openssl.*;
//import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
import org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder;

public class SslUtil
{
    static SSLSocketFactory getSocketFactory (final String caCrtFile, final String crtFile, final String keyFile,
            final String password) throws Exception
    {
        Security.addProvider(new BouncyCastleProvider());
        // load CA certificate
        PEMParser reader = new PEMParser(new InputStreamReader(new ByteArrayInputStream(Files.readAllBytes(Paths.get(caCrtFile)))));
        reader.readObject();  // skip the curve definition
        X509Certificate caCert = (X509Certificate)reader.readObject();
        reader.close();

        // load client certificate
        //        reader = new PEMReader(new InputStreamReader(new ByteArrayInputStream(Files.readAllBytes(Paths.get(crtFile)))));
        reader = new PEMParser(new InputStreamReader(new ByteArrayInputStream(Files.readAllBytes(Paths.get(crtFile)))));
        X509Certificate cert = (X509Certificate)reader.readObject();
        reader.close();

        // load client private key
        reader = new PEMParser(new InputStreamReader(new ByteArrayInputStream(Files.readAllBytes(Paths.get(keyFile)))));
        Object object = (KeyPair)reader.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);
        }
        reader.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(TrustManagerFactory.getDefaultAlgorithm());
        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");
        context.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
        return context.getSocketFactory();
    }
}

#End If
 
Upvote 0

madru

Active Member
Licensed User
Longtime User
here are the few lines of the test project.

B4X:
#Region Project Attributes 
    #MainFormWidth: 600
    #MainFormHeight: 600 
#End Region

Sub Process_Globals
    Private fx As JFX
    Private MainForm As Form
    Dim client As MqttClient
    Private Button_connect As Button
End Sub

Sub AppStart (Form1 As Form, Args() As String)
    MainForm = Form1
    MainForm.RootPane.LoadLayout("Layout1") 'Load the layout file.
    MainForm.Show
   
    'Initial cmqtt client
    client.Initialize("client", "tcp://192.168.1.10:1883", "myClientName")
   
    'setup Paho MqttCallbackExtended
    Dim Mjo As JavaObject = client
    Dim event As Object = Mjo.CreateEventFromUI("org.eclipse.paho.client.mqttv3.MqttCallback", "MqttCallback", Null)
    Mjo.GetFieldJO("client").RunMethod("setCallback", Array(event))
   
    'set Paho Keep avlive & Timeout
    Dim mo As MqttConnectOptions
    mo.Initialize("user","pass")
    Dim result As String
    Dim MqttConnectOptions1 As JavaObject = mo
    result = MqttConnectOptions1.RunMethod("setKeepAliveInterval",Array(60))
    result = MqttConnectOptions1.RunMethod("setConnectionTimeout",Array(60))

    'setup SocketFactory => does not work
    Dim jo As JavaObject = Me
    jo.InitializeNewInstance("b4j.example.main.SslUtil", Array(Null))
    Log(jo.RunMethod("setSocketFactory",jo.RunMethod("getSocketFactory", Array As String (File.GetUri(File.DirAssets, "root_ca_auth.cer"), File.GetUri(File.DirAssets, "sensor_auth.crt"), File.GetUri(File.DirAssets, "sensor_auth.key"), "pass"))))
    'result = MqttConnectOptions1.RunMethod("setSocketFactory",input from SslUtil)
End Sub


private Sub MqttCallback_Event (MethodName As String, Args() As Object ) As Object


    If MethodName = "messageArrived" Then
        Log("Message Arrived!")
        Return Null
    else If MethodName = "deliveryComplete" Then
        Log("Delivery Complete!")
        Return Null
    else If MethodName = "connectionLost" Then
        Log("Connection lost!")
        Return Null
    End If
     
End Sub

'Return true to allow the default exceptions handler to handle the uncaught exception.
Sub Application_Error (Error As Exception, StackTrace As String) As Boolean
    Return True
End Sub

#If JAVA
//import java.lang.reflect.InvocationTargetException;
import java.io.*;
import java.nio.file.*;
import java.security.*;
import java.security.cert.*;
import javax.net.ssl.*;
import org.bouncycastle.jce.provider.*;
import org.bouncycastle.openssl.*;
//import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
import org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder;

public class SslUtil
{
    public   SSLSocketFactory getSocketFactory (final String caCrtFile, final String crtFile, final String keyFile,
            final String password) throws Exception
    {
        Security.addProvider(new BouncyCastleProvider());
        // load CA certificate
        PEMParser reader = new PEMParser(new InputStreamReader(new ByteArrayInputStream(Files.readAllBytes(Paths.get(caCrtFile)))));
        reader.readObject();  // skip the curve definition
        X509Certificate caCert = (X509Certificate)reader.readObject();
        reader.close();

        // load client certificate
        //      reader = new PEMReader(new InputStreamReader(new ByteArrayInputStream(Files.readAllBytes(Paths.get(crtFile)))));
        reader = new PEMParser(new InputStreamReader(new ByteArrayInputStream(Files.readAllBytes(Paths.get(crtFile)))));
        X509Certificate cert = (X509Certificate)reader.readObject();
        reader.close();

        // load client private key
        reader = new PEMParser(new InputStreamReader(new ByteArrayInputStream(Files.readAllBytes(Paths.get(keyFile)))));
        Object object = (KeyPair)reader.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);
        }
        reader.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(TrustManagerFactory.getDefaultAlgorithm());
        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");
        context.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
        return context.getSocketFactory();
    }

}
#End If
 
Upvote 0

madru

Active Member
Licensed User
Longtime User
the project without CA etc.
 

Attachments

  • SocketFactory.zip
    3.3 KB · Views: 183
Upvote 0

madru

Active Member
Licensed User
Longtime User
getting closer......

Java code runs without any error now and outputs correct values


B4X:
#If JAVA
//import java.lang.reflect.InvocationTargetException;
import java.io.*;
import java.nio.file.*;
import java.security.*;
import java.security.cert.*;
import javax.net.ssl.*;
import org.bouncycastle.jce.provider.*;
import org.bouncycastle.openssl.*;
//import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
import org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder;

public class SslUtil
{
    public  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();
    }

}
#End If


but getting now:

B4X:
java.lang.ClassCastException: sun.security.ssl.SSLSocketFactoryImpl cannot be cast to [Ljava.lang.Object;
    at b4j.example.main._appstart(main.java:158)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at anywheresoftware.b4a.shell.Shell.runMethod(Shell.java:632)
    at anywheresoftware.b4a.shell.Shell.raiseEventImpl(Shell.java:237)
    at anywheresoftware.b4a.shell.Shell.raiseEvent(Shell.java:167)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at anywheresoftware.b4a.BA.raiseEvent2(BA.java:91)
    at anywheresoftware.b4a.shell.ShellBA.raiseEvent2(ShellBA.java:98)
    at anywheresoftware.b4a.BA.raiseEvent(BA.java:78)
    at b4j.example.main.start(main.java:47)
    at com.sun.javafx.application.LauncherImpl.lambda$launchApplication1$161(LauncherImpl.java:863)
    at com.sun.javafx.application.PlatformImpl.lambda$runAndWait$174(PlatformImpl.java:326)
    at com.sun.javafx.application.PlatformImpl.lambda$null$172(PlatformImpl.java:295)
    at java.security.AccessController.doPrivileged(Native Method)
    at com.sun.javafx.application.PlatformImpl.lambda$runLater$173(PlatformImpl.java:294)
    at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:95)
    at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
    at com.sun.glass.ui.win.WinApplication.lambda$null$147(WinApplication.java:177)
    at java.lang.Thread.run(Thread.java:748)
 
Upvote 0

Daestrum

Expert
Licensed User
Longtime User
Try changing (not 100% sure it's the problem)

B4X:
 Log(jo.RunMethod("setSocketFactory",jo.RunMethod("getSocketFactory", Array As String (File.GetUri(File.DirAssets, "root_ca_auth.cer"), File.GetUri(File.DirAssets, "sensor_auth.crt"), File.GetUri(File.DirAssets, "sensor_auth.key"), "pass"))))

to - (Array added before the getSocketFactory method call)
B4X:
 Log(jo.RunMethod("setSocketFactory",Array(jo.RunMethod("getSocketFactory", Array As String (File.GetUri(File.DirAssets, "root_ca_auth.cer"), File.GetUri(File.DirAssets, "sensor_auth.crt"), File.GetUri(File.DirAssets, "sensor_auth.key"), "pass")))))
 
Upvote 0
Top