我正在尝试通过 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, 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;
}
}