轉載請以連接形式標明出處: 本文出自:103style的博客html
修正記錄:java
2019/11/05 13:12 : 修改證書驗證內容,目前雙向驗證還有問題 flutter issues 44164android
2019/11/05 17:26 : 修改證書驗證內容,處理雙向驗證失敗的問題。git
Unhandled Exception: FileSystemException: Cannot open file, path = '...'
(OS Error: No such file or directory, errno = 2)
TlsException: Failure trusting builtin roots
SocketException: OS Error: Connection reset by peer, errno = 104
複製代碼
flutter doctor -vgithub
>flutter doctor -v
[√] Flutter (Channel stable, v1.9.1+hotfix.5, on Microsoft Windows [Version 10.0.17134.1006], locale zh-CN)
• Flutter version 1.9.1+hotfix.5 at D:\flutter
• Framework revision 1aedbb1835 (2 weeks ago), 2019-10-17 08:37:27 -0700
• Engine revision b863200c37
• Dart version 2.5.0
[√] Android toolchain - develop for Android devices (Android SDK version 28.0.3)
• Android SDK at D:\Android\sdk
• Android NDK location not configured (optional; useful for native profiling support)
• Platform android-29, build-tools 28.0.3
• Java binary at: D:\Android\AndroidStudio\jre\bin\java
• Java version OpenJDK Runtime Environment (build 1.8.0_202-release-1483-b03)
• All Android licenses accepted.
[√] Android Studio (version 3.5)
• Android Studio at D:\Android\AndroidStudio
• Flutter plugin version 40.2.2
• Dart plugin version 191.8593
• Java version OpenJDK Runtime Environment (build 1.8.0_202-release-1483-b03)
複製代碼
首先來到 flutter package 這個 flutter 相關的庫網站,而後搜了下 mqtt,找到 mqtt_client 這個庫。 上面有提供如下沒有安全認證的使用示例。 原示例地址:pub.flutter-io.cn/packages/mq…docker
import 'dart:async';
import 'dart:io';
import 'package:mqtt_client/mqtt_client.dart';
///服務器地址是 test.mosquitto.org , 端口默認是1883
///自定義端口能夠調用 MqttClient.withPort(服務器地址, 身份標識, 端口號);
final MqttClient client = MqttClient('test.mosquitto.org', '');
Future<int> main() async {
client.logging(on: false);///是否開啓日誌
client.keepAlivePeriod = 20;///設置超時時間
client.onDisconnected = onDisconnected;//設置斷開鏈接的回調
client.onConnected = onConnected;//設置鏈接成功的回調
client.onSubscribed = onSubscribed;//訂閱的回調
client.pongCallback = pong;//ping的回調
try {
await client.connect(); ///開始鏈接
} on Exception catch (e) {
print('EXAMPLE::client exception - $e');
client.disconnect();
}
///檢查鏈接結果
if (client.connectionStatus.state == MqttConnectionState.connected) {
print('EXAMPLE::Mosquitto client connected');
} else {
print('EXAMPLE::ERROR Mosquitto client connection failed - disconnecting, status is ${client.connectionStatus}');
client.disconnect();
exit(-1);
}
///訂閱一個topic: 服務端定義的事件 當服務器發送了這個消息,就會在 client.updates.listen 中收到
const String topic = 'test/lol';
client.subscribe(topic, MqttQos.atMostOnce);
///監聽服務器發來的信息
client.updates.listen((List<MqttReceivedMessage<MqttMessage>> c) {
final MqttPublishMessage recMess = c[0].payload;
///服務器返回的數據信息
final String pt = MqttPublishPayload.bytesToStringAsString(recMess.payload.message);
print( 'EXAMPLE::Change notification:: topic is <${c[0].topic}>, payload is <-- $pt -->');
});
///設置public監聽,當咱們調用 publishMessage 時,會告訴你是都發布成功
client.published.listen((MqttPublishMessage message) {
print('EXAMPLE::Published notification:: topic is ${message.variableHeader.topicName}, with Qos ${message.header.qos}');
});
///發送消息給服務器的示例
const String pubTopic = 'Dart/Mqtt_client/testtopic';
final MqttClientPayloadBuilder builder = MqttClientPayloadBuilder();
builder.addString('Hello from mqtt_client');///這裏傳 請求信息的json字符串
client.publishMessage(pubTopic, MqttQos.exactlyOnce, builder.payload);
///解除訂閱
client.unsubscribe(topic);
///斷開鏈接
client.disconnect();
return 0;
}
void onSubscribed(String topic) {
print('EXAMPLE::Subscription confirmed for topic $topic');
}
void onDisconnected() {
print('EXAMPLE::OnDisconnected client callback - Client disconnection');
if (client.connectionStatus.returnCode == MqttConnectReturnCode.solicited) {
print('EXAMPLE::OnDisconnected callback is solicited, this is correct');
}
exit(-1);
}
void onConnected() {
print('EXAMPLE::OnConnected client callback - Client connection was sucessful');
}
void pong() {
print('EXAMPLE::Ping response client callback invoked');
}
複製代碼
而後我就按這個示例跑瞭如下,提供的這個測試服務器是能夠鏈接成功的。json
可是我這邊服務器作了證書驗證,須要配置證書,而後就找到 mqtt_client 這個庫的github地址.api
而後在 issue 107 中發現 做者有提供配置證書的示例。 示例地址: github.com/shamblett/m…安全
做者在 /example/pem 這個目錄下提供了一個證書的文件, 而後經過 flutter 提供的 context.setTrustedCertificates(filepath)
設置證書。主要邏輯以下:bash
import 'dart:async';
import 'dart:io';
import 'package:path/path.dart' as path;
import 'package:mqtt_client/mqtt_client.dart';
Future<int> main() async {
...
client.secure = true;
final String currDir =
'${path.current}${path.separator}example${path.separator}';
final SecurityContext context = SecurityContext.defaultContext;
context.setTrustedCertificates(currDir + path.join('pem', 'roots.pem'));
client.securityContext = context;
client.setProtocolV311();
await client.connect();
...
return 0;
}
複製代碼
而後跑起來就發現了第一個問題:
Unhandled Exception: FileSystemException: Cannot open file, path = '...'
(OS Error: No such file or directory, errno = 2)
複製代碼
而後我就在 issue 107 下問了這個庫的做者,issue 那裏能夠看到咱們的對話,庫的做者最後說時 flutter 的 不支持 //crt/crt/cilent.crt
這種路徑的訪問。
我也嘗試了 經過配置 assets 來訪問,可是也沒有相應獲取路徑的方法。
而後我就來到 flutter 的 github 地址那提了這個 issue:flutter/issues/43472,然而到目前 2019/11/01 16:30
爲止,flutter 開發人員並無提供相關的解決方案。
而後,最後我就想,即然讀不了工程裏面的文件,我就先寫到手機文件系統中去,而後再獲取這個文件的路徑。 參考官方的 文件讀寫教程. 以下:
/// 獲取證書的本地路徑
Future<String> _getLocalFile(String filename,
{bool deleteExist: false}) async {
String dir = (await getApplicationDocumentsDirectory()).path;
log('dir = $dir');
File file = new File('$dir/$filename');
bool exist = await file.exists();
log('exist = $exist');
if (deleteExist) {
if (exist) {
file.deleteSync();
}
exist = false;
}
if (!exist) {
log("MqttUtils: start write cert in local");
await file.writeAsString(mqtt_cert);///mqtt_cert 爲證書裏面對應的內容
}
return file.path;
}
複製代碼
更新於 2019/11/05 17:26 START
而後修改鏈接的代碼爲:
_client.secure = true;
final SecurityContext context = SecurityContext.defaultContext;
String caPath =
await _getLocalFile("ca.pem", cert_ca, deleteExist: deleteExist);
String clientKeyPath = await _getLocalFile("clientkey.pem", cert_client_key,
deleteExist: deleteExist);
String clientCrtPath = await _getLocalFile("client.pem", cert_client_crt,
deleteExist: deleteExist);
try {
context.setTrustedCertificates(caPath);
context.useCertificateChain(clientCrtPath);
context.usePrivateKey(clientKeyPath);
} on Exception catch (e) {
//出現異常 嘗試刪除本地證書而後從新寫入證書
log("SecurityContext set error : " + e.toString());
return -1;
}
_client.securityContext = context;
_client.setProtocolV311();
複製代碼
上面代碼的幾個字符串分別表明: cert_ca:根證書的內容 cert_client_key:客戶端私鑰的內容 cert_client_crt:客戶端證書的內容
更新於 2019/11/05 17:26 END
證書內容不對的話會報如下錯誤:
TlsException: Failure trusting builtin roots
複製代碼
更新於 2019/11/05 13:12 START
SecurityContext 有提供直接也內容的方法,並不必定要傳路徑...
也能夠在配置 assets 經過如下方法讀取內容,
String cerData = await rootBundle.loadString("assets/cc.pem");
utf8.encode(caPath);
複製代碼
而後調用如下帶 bytes 的方法便可。
void usePrivateKey(String file, {String password});
void usePrivateKeyBytes(List<int> keyBytes, {String password});
void setTrustedCertificates(String file, {String password});
void setTrustedCertificatesBytes(List<int> certBytes, {String password});
void useCertificateChain(String file, {String password});
void useCertificateChainBytes(List<int> chainBytes, {String password});
void setClientAuthorities(String file, {String password});
void setClientAuthoritiesBytes(List<int> authCertBytes, {String password});
複製代碼
更新於 2019/11/05 13:12 END
而後好好的用了幾天,昨天下午鏈接的時候忽然又鏈接不上了!!! 報錯提示:
SocketException: OS Error: Connection reset by peer, errno = 104
複製代碼
而後搜索了一圈,又去問 mqtt_client 庫的做者... mqtt_client/issues/131. 而後他也沒遇到過。
最後經過 Wireshark 抓包發現報錯信息 TLSv1.2 Handshake failure
, 而後經過服務端哪些查看報錯信息,而後搜索一圈發現多是 docker 1.6.3版本 官方鏡像的問題,也多是昨天下午服務端同事改了配置重啓以後致使的,感受應該是後者....
最後發現是本身證書配置的問題! 上面的代碼示例 和 demo中的已修正! 以前能連上是由於服務端沒有配置雙向驗證。
最後提供一個Demo: github.com/103style/mq…
以上
若是以爲不錯的話,請幫忙點個讚唄。
掃描下面的二維碼,關注個人公衆號 Android1024, 點關注,不迷路。