Android Java Paho MQTT 连接谷歌物联网核心附谷歌根CA认证包

如何解决Android Java Paho MQTT 连接谷歌物联网核心附谷歌根CA认证包

我正在尝试通过 paho mqtt 库连接来自 android 的 google iot 核心。我能够连接并启动发布。但是就在连接之后它断开连接并且发布失败,但连接丢失除外。经过一番谷歌搜索后,我发现我必须在 mqtt 客户端上附加 Google 根 CA 认证包。经过一番研究,我找到了一种向 MqttConnectOptions 提及自定义套接字工厂(需要证书)的方法。现在的问题是我遇到了以下异常:

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

请帮帮我。 这是android代码的详细信息: 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>

项目级 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
}

模块级 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,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.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",InetAddress localHost,int localPort) throws IOException {
        SSLSocket r = (SSLSocket)this.factory.createSocket(host,localHost,localPort);
        r.setEnabledProtocols(new String[] {"TLSv1","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.2"});
        return r;
    }

    @Override
    public Socket createSocket(InetAddress address,InetAddress localAddress,int localPort) throws IOException {
        SSLSocket r = (SSLSocket)this.factory.createSocket(address,localAddress,"TLSv1.2"});
        return r;
    }
}

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


依赖报错 idea导入项目后依赖报错,解决方案:https://blog.csdn.net/weixin_42420249/article/details/81191861 依赖版本报错:更换其他版本 无法下载依赖可参考:https://blog.csdn.net/weixin_42628809/a
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下 2021-12-03 13:33:33.927 ERROR 7228 [ main] o.s.b.d.LoggingFailureAnalysisReporter : *************************** APPL
错误1:gradle项目控制台输出为乱码 # 解决方案:https://blog.csdn.net/weixin_43501566/article/details/112482302 # 在gradle-wrapper.properties 添加以下内容 org.gradle.jvmargs=-Df
错误还原:在查询的过程中,传入的workType为0时,该条件不起作用 &lt;select id=&quot;xxx&quot;&gt; SELECT di.id, di.name, di.work_type, di.updated... &lt;where&gt; &lt;if test=&qu
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct redisServer’没有名为‘server_cpulist’的成员 redisSetCpuAffinity(server.server_cpulist); ^ server.c: 在函数‘hasActiveC
解决方案1 1、改项目中.idea/workspace.xml配置文件,增加dynamic.classpath参数 2、搜索PropertiesComponent,添加如下 &lt;property name=&quot;dynamic.classpath&quot; value=&quot;tru
删除根组件app.vue中的默认代码后报错:Module Error (from ./node_modules/eslint-loader/index.js): 解决方案:关闭ESlint代码检测,在项目根目录创建vue.config.js,在文件中添加 module.exports = { lin
查看spark默认的python版本 [root@master day27]# pyspark /home/software/spark-2.3.4-bin-hadoop2.7/conf/spark-env.sh: line 2: /usr/local/hadoop/bin/hadoop: No s
使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -&gt; systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping(&quot;/hires&quot;) public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate&lt;String
使用vite构建项目报错 C:\Users\ychen\work&gt;npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-