B4J Question Using AWS IoT Core MQTT Broker with B4X

Ralph Parkhurst

Member
Licensed User
Longtime User
I wish to incorporate an MQTT client into my B4A and B4I mobile apps using Amazon's "IoT Core" cloud based MQTT broker. I've successfully used other MQTT clients/brokers that authenticate using username and password, but AWS use X.509 certificates which I'm finding a lot harder to understand.

From AWS website "X.509 certificates provide AWS IoT with the ability to authenticate client and device connections. Client certificates must be registered with AWS IoT before a client can communicate with AWS IoT."

So to help me to better understand, I used the examples, tutorials and resources found on this forum to successfully create and test an MQTT client that connects/publishes/subscribes to AWS IoT Core using B4J. This code is below for anyone else trying to achieve a similar outcome, although you'll have to use your own CA, cert and key files :).

Where I need help is determining how to migrate this working code example from B4J to B4A and B4I.

Any help or recommendation would be very warmly appreciated.
 

Attachments

  • AWS_IOT_CORE_EXAMPLE.zip
    4 KB · Views: 92

Ralph Parkhurst

Member
Licensed User
Longtime User
Well, it turned out to be very easy to migrate this from B4J to B4A.

However, it does not work :( because I read here Android does not support BouncyCastle due to a class clash:

Here is a B4A sample in case someone knows how to use "SpongyCastle" to connect to AWS IoT Core.

B4X:
#Region  Project Attributes
    #ApplicationLabel: B4A Example
    #VersionCode: 1
    #VersionName:
    'SupportedOrientations possible values: unspecified, landscape or portrait.
    #SupportedOrientations: unspecified
    #CanInstallToExternalStorage: False
    #AdditionalJar: bcpkix-jdk18on-176
    #AdditionalJar: bcprov-jdk18on-176
#End Region

#Region  Activity Attributes
    #FullScreen: False
    #IncludeTitle: True
    #BridgeLogger: True
#End Region

Sub Process_Globals
    'These global variables will be declared once when the application starts.
    'These variables can be accessed from all modules.
    Private xui As XUI
    Public client As MqttClient ' uses library: jMQTT
    Public mo As MqttConnectOptions
    Public topic, payload As String
End Sub

Sub Globals
    'These global variables will be redeclared each time the activity is created.
End Sub

Sub Activity_Create(FirstTime As Boolean)
    Activity.LoadLayout("Layout")
End Sub

Sub Activity_Resume

End Sub

Sub Activity_Pause (UserClosed As Boolean)

End Sub

Sub Button1_Click
    topic =     "Sample_Topic"
    payload =     "Sample_Payload"
    
    File.Copy(File.DirAssets, "cafile.pem",File.DirInternal,"cafile.pem")
    File.Copy(File.DirAssets, "certfile.cer",File.DirInternal,"certfile.cer")
    File.Copy(File.DirAssets, "keyfile.key",File.DirInternal,"keyfile.key")
    
    Dim cafile As String =  xui.fileUri(File.DirInternal,"cafile.pem").SubString(7)
    Dim certfile As String = xui.FileUri(File.DirInternal,"certfile.cer").SubString(7)
    Dim keyfile As String =  xui.FileUri(File.DirInternal,"keyfile.key").SubString(7)
    
    Dim username As String = ""
    Dim password As String = ""
    Dim keyFilePassword As String = Null
    
    Log(cafile)
    Log(certfile)
    Log(keyfile)
    
    client.Initialize("client", "ssl://axxxxxxxxxxxxxxx-ats.iot.ap-southeast-x.amazonaws.com:8883", "ClientID_1234")
    
    '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 Options
    mo.Initialize(username, password)
    Dim MqttConnectOptions1 As JavaObject = mo
    Dim result As String
    result = MqttConnectOptions1.RunMethod("setMqttVersion",Array(3))
    result = MqttConnectOptions1.RunMethod("setKeepAliveInterval",Array(60))
    result = MqttConnectOptions1.RunMethod("setConnectionTimeout",Array(60))
    
    'setup SocketFactory
    Dim jo As JavaObject = Me
    jo.InitializeNewInstance("b4a.example.main.SslUtil", Array(Null))
    MqttConnectOptions1.RunMethod("setSocketFactory",Array(jo.RunMethod("getSocketFactory", Array As String (cafile, certfile, keyfile, keyFilePassword))))
    
    'connect to MQTT broker
    client.Connect2(mo)
End Sub


Private Sub client_Connected (Success As Boolean)
    If Success Then
        'connercted, so now publish an MQTT message
        Log("MQTT Connected!")
        client.Publish2(topic, payload.GetBytes("UTF8"), 1, False)
        client.Subscribe(topic,0)
    Else
        Log("MQTT not connected")
    End If
End Sub


Private Sub client_Disconnected
    Log("MQTT Disconnected!")
End Sub


Private Sub MqttCallback_Event (MethodName As String, Args() As Object)
    If MethodName = "messageArrived" Then
        Log("MQTT Message Arrived!   Topic: " & Args(0) & "   Payload: " & Args(1))
    
    else If MethodName = "deliveryComplete" Then
        Log("MQTT Delivery Complete!")
        
    else If MethodName = "connectionLost" Then
        Log("MQTT Connection lost!")
    End If
End Sub



#If JAVA
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.FileReader;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManagerFactory;
import java.io.FileReader;
import java.security.KeyPair;
import java.security.KeyStore;
import java.security.Security;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
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;

public class SslUtil
{
    public  SSLSocketFactory getSocketFactory(final String caCrtFile, final String crtFile, final String keyFile,
                                                    final String password) {
        try {

            /**
             * Add BouncyCastle as a Security Provider
             */
            Security.addProvider(new BouncyCastleProvider());

            JcaX509CertificateConverter certificateConverter = new JcaX509CertificateConverter().setProvider("BC");

            /**
             * Load Certificate Authority (CA) certificate
             */
            PEMParser reader = new PEMParser(new FileReader(caCrtFile));
            X509CertificateHolder caCertHolder = (X509CertificateHolder) reader.readObject();
            reader.close();

            X509Certificate caCert = certificateConverter.getCertificate(caCertHolder);

            /**
             * Load client certificate
             */
            reader = new PEMParser(new FileReader(crtFile));
            X509CertificateHolder certHolder = (X509CertificateHolder) reader.readObject();
            reader.close();

            X509Certificate cert = certificateConverter.getCertificate(certHolder);

            /**
             * Load client private key
             */
            reader = new PEMParser(new FileReader(keyFile));
            Object keyObject = reader.readObject();
            reader.close();

            PEMDecryptorProvider provider = new JcePEMDecryptorProviderBuilder().build(password.toCharArray());
            JcaPEMKeyConverter keyConverter = new JcaPEMKeyConverter().setProvider("BC");

            KeyPair key;

            if (keyObject instanceof PEMEncryptedKeyPair) {
                key = keyConverter.getKeyPair(((PEMEncryptedKeyPair) keyObject).decryptKeyPair(provider));
            } else {
                key = keyConverter.getKeyPair((PEMKeyPair) keyObject);
            }

            /**
             * CA certificate is used to authenticate server
             */
            KeyStore caKeyStore = KeyStore.getInstance(KeyStore.getDefaultType());
            caKeyStore.load(null, null);
            caKeyStore.setCertificateEntry("ca-certificate", caCert);

            TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(
                    TrustManagerFactory.getDefaultAlgorithm());
            trustManagerFactory.init(caKeyStore);

            /**
             * Client key and certificates are sent to server so it can authenticate the client
             */
            KeyStore clientKeyStore = KeyStore.getInstance(KeyStore.getDefaultType());
            clientKeyStore.load(null, null);
            clientKeyStore.setCertificateEntry("certificate", cert);
            clientKeyStore.setKeyEntry("private-key", key.getPrivate(), password.toCharArray(),
                    new Certificate[]{cert});

            KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(
                    KeyManagerFactory.getDefaultAlgorithm());
            keyManagerFactory.init(clientKeyStore, password.toCharArray());

            /**
             * Create SSL socket factory
             */
            SSLContext context = SSLContext.getInstance("TLSv1.2");
            context.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null);

            /**
             * Return the newly created socket factory object
             */
            return context.getSocketFactory();

        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }

}
#End If
 
Upvote 0
Top