Android Java Paho MQTT connect with Google Iot Core attach Google root CA certification package

Tags: #<Tag:0x00007fc41a92a4b0> #<Tag:0x00007fc41a92a3c0>

I am trying to connect with google iot core from android by paho mqtt library. I am able to connect and initiate publish. But just after connect its got disconnected and the publish failed with the exception that connection lost. After some googling i found that i have to attach Google root CA certification package with the mqtt client. After some research i found a way to mention custom socket factory(which takes the certificate) to MqttConnectOptions. Now the problem is i got the following exception:

java.security.cert.CertificateException: 
    com.android.org.conscrypt.OpenSSLX509CertificateFactory$ParsingException: 
    com.android.org.conscrypt.OpenSSLX509CertificateFactory$ParsingException: 
    java.lang.RuntimeException: error:0c0000be:ASN.1 encoding 
    routines:OPENSSL_internal:WRONG_TAG

Please help me.
Here is the android code details:
AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.example.mqttapp">
    
        <uses-permission android:name="android.permission.INTERNET"/>
        <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
        <uses-permission android:name="android.permission.WAKE_LOCK"/>
    
        <application
            android:allowBackup="true"
            android:icon="@mipmap/ic_launcher"
            android:label="@string/app_name"
            android:roundIcon="@mipmap/ic_launcher_round"
            android:supportsRtl="true"
            android:theme="@style/Theme.MQTTApp">
            <activity android:name=".MainActivity">
                <intent-filter>
                    <action android:name="android.intent.action.MAIN" />
    
                    <category android:name="android.intent.category.LAUNCHER" />
                </intent-filter>
            </activity>
    
            <service android:name="org.eclipse.paho.android.service.MqttService"/>
    
        </application>
    
    </manifest>

Project level build.gradle

 // Top-level build file where you can add configuration options common to all sub-projects/modules.
    buildscript {
        repositories {
            google()
            jcenter()
        }
        dependencies {
            classpath "com.android.tools.build:gradle:4.1.3"
    
            // NOTE: Do not place your application dependencies here; they belong
            // in the individual module build.gradle files
        }
    }
    
    allprojects {
        repositories {
            google()
            jcenter()
            maven {
                url "https://repo.eclipse.org/content/repositories/paho-snapshots/"
            }
        }
    }
    
    task clean(type: Delete) {
        delete rootProject.buildDir
    }

module level build.gradle

plugins {
        id 'com.android.application'
    }
    
    android {
        compileSdkVersion 30
        buildToolsVersion "30.0.3"
    
        defaultConfig {
            applicationId "com.example.mqttapp"
            minSdkVersion 26
            targetSdkVersion 30
            versionCode 1
            versionName "1.0"
    
            testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        }
    
        buildTypes {
            release {
                minifyEnabled false
                proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
            }
        }
        compileOptions {
            sourceCompatibility JavaVersion.VERSION_1_8
            targetCompatibility JavaVersion.VERSION_1_8
        }
    }
    
    repositories {
        maven {
            url "https://repo.eclipse.org/content/repositories/paho-snapshots/"
        }
    }
    
    dependencies {
    
        implementation 'androidx.appcompat:appcompat:1.2.0'
        implementation 'com.google.android.material:material:1.3.0'
        implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
    
        implementation 'org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.1.0'
        implementation 'org.eclipse.paho:org.eclipse.paho.android.service:1.1.1'
        implementation 'io.jsonwebtoken:jjwt:0.7.0'
        implementation 'net.danlew:android.joda:2.10.9.1'
        implementation 'commons-cli:commons-cli:1.4'
        implementation 'org.bouncycastle:bcpkix-jdk15on:1.56'
    
    
        testImplementation 'junit:junit:4.+'
        androidTestImplementation 'androidx.test.ext:junit:1.1.2'
        androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
    }

MainActivity.java

package com.example.mqttapp;
    
    import android.os.Bundle;
    import android.util.Log;
    
    import androidx.appcompat.app.AppCompatActivity;
    
    import org.eclipse.paho.client.mqttv3.MqttClient;
    import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
    import org.eclipse.paho.client.mqttv3.MqttMessage;
    import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
    
    import java.io.InputStream;
    
    public class MainActivity extends AppCompatActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            String clientId = "projects/<project_id>/locations/<region>/registries/<registry>/devices/<device>";
    
            String topic        = "/devices/<device>/<topics>";
            String content      = "Message from MqttPublishSample";
            int qos             = 1;
            String broker       = "ssl://mqtt.googleapis.com:8883";
            MemoryPersistence persistence = new MemoryPersistence();
    
    
            try {
                IotCorePasswordGenerator passwordGenerator = new IotCorePasswordGenerator("project_id", getResources(), R.raw.private_key);
                MqttClient sampleClient = new MqttClient(broker, clientId, persistence);
                MqttConnectOptions connOpts = new MqttConnectOptions();
                connOpts.setUserName("unused");
                InputStream inputStream = getResources().openRawResource(R.raw.roots);
    
                SocketFactory.SocketFactoryOptions socketFactoryOptions = new SocketFactory.SocketFactoryOptions();
                try {
                    socketFactoryOptions.withCaInputStream(getResources().openRawResource(R.raw.roots));
                    connOpts.setSocketFactory(new SocketFactory(socketFactoryOptions));
                } catch (Exception e) {
                    *e.printStackTrace();*//exception occurs here
                }
                connOpts.setPassword(passwordGenerator.createJwtRsaPassword());
                connOpts.setCleanSession(true);
                connOpts.setMqttVersion(MqttConnectOptions.MQTT_VERSION_3_1_1);
                Log.d("tag_bug_bug","Connecting to broker: "+broker);
                sampleClient.connect(connOpts);
                Log.d("tag_bug_bug","Connected");
                Log.d("tag_bug_bug","Publishing message: "+content);
                MqttMessage message = new MqttMessage(content.getBytes());
                message.setQos(qos);
                sampleClient.publish(topic, message);
                Log.d("tag_bug_bug","Message published");
                //sampleClient.disconnect();
                Log.d("tag_bug_bug","Disconnected");
                //System.exit(0);
            } catch(Exception me) {
                Log.d("tag_bug_bug","msg "+me.getMessage());
                Log.d("tag_bug_bug","loc "+me.getLocalizedMessage());
                Log.d("tag_bug_bug","cause "+me.getCause());
                Log.d("tag_bug_bug","excep "+me);
                me.printStackTrace();
            }
        }
    }

IotCorePasswordGenerator.java

package com.example.mqttapp;
    
    import android.content.res.Resources;
    import android.util.Base64;
    
    import java.io.IOException;
    import java.io.InputStream;
    import java.security.KeyFactory;
    import java.security.NoSuchAlgorithmException;
    import java.security.cert.CertificateException;
    import java.security.cert.CertificateFactory;
    import java.security.spec.InvalidKeySpecException;
    import java.security.spec.PKCS8EncodedKeySpec;
    import java.time.Duration;
    import java.time.Instant;
    import java.util.Date;
    import java.security.cert.X509Certificate;
    
    import io.jsonwebtoken.JwtBuilder;
    import io.jsonwebtoken.Jwts;
    import io.jsonwebtoken.SignatureAlgorithm;
    
    class IotCorePasswordGenerator {
     
        private final String projectId;
        private final Resources resources;
        private final int privateKeyRawFileId;
     
        IotCorePasswordGenerator(String projectId, Resources resources, int privateKeyRawFileId) {
            this.projectId = projectId;
            this.resources = resources;
            this.privateKeyRawFileId = privateKeyRawFileId;
        }
     
        char[] createJwtRsaPassword() {
            try {
                byte[] privateKeyBytes = decodePrivateKey(resources, privateKeyRawFileId);
                return createJwtRsaPassword(projectId, privateKeyBytes).toCharArray();
            } catch (NoSuchAlgorithmException e) {
                throw new IllegalStateException("Algorithm not supported. (developer error)", e);
            } catch (InvalidKeySpecException e) {
                throw new IllegalStateException("Invalid Key spec. (developer error)", e);
            } catch (IOException e) {
                throw new IllegalStateException("Cannot read private key file.", e);
            }
        }
     
        private static byte[] decodePrivateKey(Resources resources, int privateKeyRawFileId) throws IOException {
            try(InputStream inStream = resources.openRawResource(privateKeyRawFileId)) {
                return Base64.decode(inputToString(inStream), Base64.DEFAULT);
            }
        }
     
        private static String inputToString(InputStream is) {
            java.util.Scanner s = new java.util.Scanner(is).useDelimiter("\\A");
            return s.hasNext() ? s.next() : "";
        }
     
        private static String createJwtRsaPassword(String projectId, byte[] privateKeyBytes) throws NoSuchAlgorithmException, InvalidKeySpecException {
            try {
                CertificateFactory fact = CertificateFactory.getInstance("X.509");
            } catch (CertificateException e) {
                e.printStackTrace();
            }
            return createPassword(projectId, privateKeyBytes, "RSA", SignatureAlgorithm.RS256);
        }
     
        private static String createPassword(String projectId, byte[] privateKeyBytes, String algorithmName, SignatureAlgorithm signatureAlgorithm) throws NoSuchAlgorithmException, InvalidKeySpecException {
            Instant now = Instant.now();
            // The device will be disconnected after the token expires, and will have to reconnect with a new token. 
            // The audience field should always be set to the GCP project id.
            JwtBuilder jwtBuilder =
                    Jwts.builder()
                            .setIssuedAt(Date.from(now))
                            .setExpiration(Date.from(now.plus(Duration.ofMinutes(20))))
                            .setAudience(projectId);
     
            PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(privateKeyBytes);
            KeyFactory kf = KeyFactory.getInstance(algorithmName);
     
            return jwtBuilder.signWith(signatureAlgorithm, kf.generatePrivate(spec)).compact();
        }
     
    }

SocketFactory.java

package com.example.mqttapp;
    
    import android.util.Log;
    
    import java.io.IOException;
    import java.io.InputStream;
    import java.net.InetAddress;
    import java.net.Socket;
    import java.security.KeyManagementException;
    import java.security.KeyStore;
    import java.security.KeyStoreException;
    import java.security.NoSuchAlgorithmException;
    import java.security.NoSuchProviderException;
    import java.security.UnrecoverableKeyException;
    import java.security.cert.CertificateFactory;
    import java.security.cert.X509Certificate;
    import java.util.Enumeration;
    
    import javax.net.ssl.KeyManagerFactory;
    import javax.net.ssl.SSLContext;
    import javax.net.ssl.SSLSocket;
    import javax.net.ssl.TrustManager;
    import javax.net.ssl.TrustManagerFactory;
    import javax.security.cert.CertificateException;
    
    /**
     * Original SocketFactory file taken from https://github.com/owntracks/android
     */
    
    public class SocketFactory extends javax.net.ssl.SSLSocketFactory{
        private javax.net.ssl.SSLSocketFactory factory;
    
        public static class SocketFactoryOptions {
    
            private InputStream caCrtInputStream;
            private InputStream caClientP12InputStream;
            private String caClientP12Password;
    
            public SocketFactoryOptions withCaInputStream(InputStream stream) {
                this.caCrtInputStream = stream;
                return this;
            }
            public SocketFactoryOptions withClientP12InputStream(InputStream stream) {
                this.caClientP12InputStream = stream;
                return this;
            }
            public SocketFactoryOptions withClientP12Password(String password) {
                this.caClientP12Password = password;
                return this;
            }
    
            public boolean hasCaCrt() {
                return caCrtInputStream != null;
            }
    
            public boolean hasClientP12Crt() {
                return caClientP12Password != null;
            }
    
            public InputStream getCaCrtInputStream() {
                return caCrtInputStream;
            }
    
            public InputStream getCaClientP12InputStream() {
                return caClientP12InputStream;
            }
    
            public String getCaClientP12Password() {
                return caClientP12Password;
            }
    
            public boolean hasClientP12Password() {
                return (caClientP12Password != null) && !caClientP12Password.equals("");
            }
        }
        public SocketFactory() throws CertificateException, KeyStoreException, NoSuchAlgorithmException, IOException, KeyManagementException, java.security.cert.CertificateException, UnrecoverableKeyException {
            this(new SocketFactoryOptions());
        }
    
    
        private TrustManagerFactory tmf;
    
        public SocketFactory(SocketFactoryOptions options) throws KeyStoreException, NoSuchAlgorithmException, IOException, KeyManagementException, java.security.cert.CertificateException, UnrecoverableKeyException {
            Log.v(this.toString(), "initializing CustomSocketFactory");
    
            tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
            KeyManagerFactory kmf = null;
            kmf = KeyManagerFactory.getInstance("X509");
    
    
            if(options.hasCaCrt()) {
                Log.v(this.toString(), "MQTT_CONNECTION_OPTIONS.hasCaCrt(): true");
    
                KeyStore caKeyStore = KeyStore.getInstance(KeyStore.getDefaultType());
                caKeyStore.load(null, null);
    
                CertificateFactory caCF = CertificateFactory.getInstance("X.509");
                X509Certificate ca = (X509Certificate) caCF.generateCertificate(options.getCaCrtInputStream());
                String alias = ca.getSubjectX500Principal().getName();
                // Set propper alias name
                caKeyStore.setCertificateEntry(alias, ca);
                tmf.init(caKeyStore);
                Enumeration<String> aliasesCA = caKeyStore.aliases();
                for (; aliasesCA.hasMoreElements(); ) {
                    String o = aliasesCA.nextElement();
                }
    
    
    
            } else {
                KeyStore keyStore = KeyStore.getInstance("AndroidCAStore");
                keyStore.load(null);
                tmf.init(keyStore);
            }
    
            if (options.hasClientP12Crt()) {
                Log.v(this.toString(), "MQTT_CONNECTION_OPTIONS.hasClientP12Crt(): true");
    
                KeyStore clientKeyStore = KeyStore.getInstance("PKCS12");
    
                clientKeyStore.load(options.getCaClientP12InputStream(), options.hasClientP12Password() ? options.getCaClientP12Password().toCharArray() : new char[0]);
                kmf.init(clientKeyStore, options.hasClientP12Password() ? options.getCaClientP12Password().toCharArray() : new char[0]);
    
                Log.v(this.toString(), "Client .p12 Keystore content: ");
                Enumeration<String> aliasesClientCert = clientKeyStore.aliases();
                for (; aliasesClientCert.hasMoreElements(); ) {
                    String o = aliasesClientCert.nextElement();
                }
            } else {
                Log.v(this.toString(), "Client .p12 sideload: false, using null CLIENT cert");
                kmf.init(null,null);
            }
    
            // Create an SSLContext that uses our TrustManager
            SSLContext context = SSLContext.getInstance("TLSv1.2");
            context.init(kmf.getKeyManagers(), getTrustManagers(), null);
            this.factory= context.getSocketFactory();
    
        }
    
        public TrustManager[] getTrustManagers() {
            return tmf.getTrustManagers();
        }
    
        @Override
        public String[] getDefaultCipherSuites() {
            return this.factory.getDefaultCipherSuites();
        }
    
        @Override
        public String[] getSupportedCipherSuites() {
            return this.factory.getSupportedCipherSuites();
        }
    
        @Override
        public Socket createSocket() throws IOException {
            SSLSocket r = (SSLSocket)this.factory.createSocket();
            r.setEnabledProtocols(new String[] {"TLSv1", "TLSv1.1", "TLSv1.2"});
            return r;
        }
    
        @Override
        public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
            SSLSocket r = (SSLSocket)this.factory.createSocket(s, host, port, autoClose);
            r.setEnabledProtocols(new String[] {"TLSv1", "TLSv1.1", "TLSv1.2"});
            return r;
        }
    
        @Override
        public Socket createSocket(String host, int port) throws IOException {
    
            SSLSocket r = (SSLSocket)this.factory.createSocket(host, port);
            r.setEnabledProtocols(new String[] {"TLSv1", "TLSv1.1", "TLSv1.2"});
            return r;
        }
    
        @Override
        public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException {
            SSLSocket r = (SSLSocket)this.factory.createSocket(host, port, localHost, localPort);
            r.setEnabledProtocols(new String[] {"TLSv1", "TLSv1.1", "TLSv1.2"});
            return r;
        }
    
        @Override
        public Socket createSocket(InetAddress host, int port) throws IOException {
            SSLSocket r = (SSLSocket)this.factory.createSocket(host, port);
            r.setEnabledProtocols(new String[]{ "TLSv1", "TLSv1.1", "TLSv1.2"});
            return r;
        }
    
        @Override
        public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
            SSLSocket r = (SSLSocket)this.factory.createSocket(address, port, localAddress,localPort);
            r.setEnabledProtocols(new String[] {"TLSv1", "TLSv1.1", "TLSv1.2"});
            return r;
        }
    }

This is not a support forum for Google Assistant or Android app development. It is a forum for Home Assistant, an open source home automation system.