本博文的重點是嘗試CoAP協議的應用開發,其中包含CoAP協議中一個重要的開源工具libcoap的安裝和遇到的問題調研。固然,爲了很好的將EMQ的CoAP協議網關用起來,也調研了下EMQ體系下,CoAP的使用邏輯, CoAP支持明文,也支持DTLS的安全傳輸。html
首先,介紹下libcoap的環境準備,而後基於libcoap進行EMQ的CoAP協議支持的驗證。個人環境信息以下:java
1. Linux: 3.10.0-514.el7.x86_64 #1 SMP Tue Nov 22 16:42:41 UTC 2016 x86_64 x86_64 x86_64 GNU/Linuxlinux
2. libcoap: 4.1.1git
3. EMQ:起初10.95.200.11上是EMQ v2.3.11,後來驗證coap沒法正常工做,將EMQ的V3版本即emqx v3.0.1在10.95.197.8上安裝,再次驗證coap工做。github
爲了驗證後面的COAPS通訊,即CoAP基於DTLS的安全通訊,這裏,將libcoap的SSL環境也作一下準備,libcoap支持多種SSL的組件,這裏,選擇最爲基礎的且是最爲經常使用的組件openssl。算法
1. libcoap帶安全組件的環境構建centos
1) 首先安裝openssl, 須要的openssl的版本比較高點,操做系統原始帶有的openssl版本爲1.0.1,安裝openssl也比較簡單,就很少說,我這裏用的是openssl-1.1.1b.tar.gz。安全
[root@tkwh-kfcs-app1 libcoap]# openssl version OpenSSL 1.1.1b 26 Feb 2019
2) 將libcoap-4.1.1.tar.gz從官網下載後,解壓而後執行配置,以下。服務器
[root@tkwh-kfcs-app1 libcoap]# ./configure --enable-documentation=no --enable-tests=no --with-openssl openssl: error while loading shared libraries: libssl.so.1.1: cannot open shared object file: No such file or directory
遇到圖中所示的錯誤,這個錯誤是說動態連接庫依賴找不到,其實,這種問題,一般是構建軟鏈接便可解決,由於openssl安裝好的話,ssl的動態連接庫都是會有的。將本身安裝的openssl的動態連接庫創建一個軟鏈接,放在/usr/lib64目錄下。app
ln -s /usr/local/lib64/libssl.so.1.1 /usr/lib64/libssl.so.1.1 ln -s /usr/local/lib64/libcrypto.so.1.1 /usr/lib64/libcrypto.so.1.1
3) 再次執行libcoap的安裝
[root@tkwh-kfcs-app1 libcoap]# ./configure --enable-documentation=no --enable-tests=no --with-openssl configure: error: ==> OpenSSL 1.0.1e too old. OpenSSL >= 1.1.0 required for suitable DTLS support build
檢查模塊版本號:
[root@tkwh-kfcs-app1 libcoap]# pkg-config --modversion openssl 1.0.1e
查看libcoap中的configure文件發現,系統查找路徑使用的pkg-configure
[root@tkwh-kfcs-app1 libcoap]# find / -name pkgconfig /usr/lib64/pkgconfig /usr/share/pkgconfig /usr/local/lib64/pkgconfig /usr/local/openssl/lib/pkgconfig
結合上面安裝openssl的時間(4月21)查看,新安裝的openssl對應的pkgconfig應該是/usr/local/lib64/pkgconfig或者/usr/local/openssl/lib/pkgconfig
[tkiot@tkwh-kfcs-app1 openssl-1.1.1b]$ cd /usr/local/openssl/lib/pkgconfig [tkiot@tkwh-kfcs-app1 pkgconfig]$ ll total 12 -rw-r--r--. 1 root root 299 Apr 21 09:20 libcrypto.pc -rw-r--r--. 1 root root 278 Apr 21 09:20 libssl.pc -rw-r--r--. 1 root root 232 Apr 21 09:20 openssl.pc [tkiot@tkwh-kfcs-app1 pkgconfig]$ cd /usr/local/lib64/pkgconfig [tkiot@tkwh-kfcs-app1 pkgconfig]$ ll total 12 -rw-r--r--. 1 root root 293 Apr 21 09:26 libcrypto.pc -rw-r--r--. 1 root root 272 Apr 21 09:26 libssl.pc -rw-r--r--. 1 root root 226 Apr 21 09:26 openssl.pc [tkiot@tkwh-kfcs-app1 pkgconfig]$
配置一下包環境變量PKG_CONFIG_PATH
[root@tkwh-kfcs-app1 libcoap]# export PKG_CONFIG_PATH=$PKG_CONFIG_PATH:/usr/local/lib64/pkgconfig [root@tkwh-kfcs-app1 libcoap]# [root@tkwh-kfcs-app1 libcoap]# pkg-config -modversion openssl Unknown option -modversion [root@tkwh-kfcs-app1 libcoap]# pkg-config --modversion openssl 1.1.1b
4) 再次執行libcoap的配置安裝環境
[root@tkwh-kfcs-app1 libcoap]# ./configure --enable-documentation=no --enable-tests=no --with-openssl 。。。。 libcoap configuration summary: libcoap package version : "4.2.0" libcoap library version : "1.0.1" libcoap API version : "2" libcoap DTLS lib extn : "-openssl" host system : "x86_64-unknown-linux-gnu" build DTLS support : "yes" --> OpenSSL around : "yes" (found OpenSSL 1.1.1b) OPENSSL_CFLAGS : "-I/usr/local/include " OPENSSL_LIBS : "-L/usr/local/lib64 -lssl -lcrypto " build doxygen pages : "no" build man pages : "no" build unit test binary : "no" build examples : "yes" build with gcov support : "no" [root@tkwh-kfcs-app1 libcoap]#
2. 配置emq的coap環境
其實很簡單,就是一個plugin。
## Value: Port coap.port = 5683 ## Interval for keepalive, specified in seconds. ## ## Value: Duration ## -s: seconds ## -m: minutes ## -h: hours coap.keepalive = 120s ## Whether to enable statistics for CoAP clients. ## ## Value: on | off coap.enable_stats = off ## Private key file for DTLS ## ## Value: File coap.keyfile = /opt/certs/ecc/eccEmqCertPem.key ## Server certificate for DTLS. ## ## Value: File coap.certfile = /opt/certs/ecc/eccEmqCert.crt
上述的coap的keyfile和certfile的內容,是基於上一篇博文MQTT研究之EMQ:【CoAP協議的ECC證書研究】建立出來的。
而後執行加載插件指令:
[root@tkwh-kfcs-app1 emqttd]#./bin/emqttd_ctl plugins load emq_coap
用nc指令檢查端口是否活着(注意,首先想到的Telnet指令,是不行的,Telnet是檢查TCP端口的)
[root@tkwh-kfcs-app1 emqttd]# nc -vu 10.95.200.11 5683 Ncat: Version 6.40 ( http://nmap.org/ncat ) Ncat: Connected to 10.95.200.11:5683.
到此,說明EMQ的CoAP環境構建成功,且CoAP的客戶端測試環境libcoap也已經成功搭建。
3. CoAP基本的返回碼信息介紹
CoAP協議是相似HTTP的風格的協議,只是底層是基於UDP和HTTP底層是基於TCP的協議。 在CoAP響應中,Code被定義爲CoAP響應碼,相似於HTTP 200 OK等等。
【2.01】Created 【2.02】Deleted 【2.03】Valid 【2.04】Changed 【2.05】Content。相似於HTTP 200 OK 【4.00】Bad Request 請求錯誤,服務器沒法處理。相似於HTTP 400。 【4.01】Unauthorized 沒有範圍權限。相似於HTTP 401。 【4.02】Bad Option 請求中包含錯誤選項。 【4.03】Forbidden 服務器拒絕請求。相似於HTTP 403。 【4.04】Not Found 服務器找不到資源。相似於HTTP 404。 【4.05】Method Not Allowed 非法請求方法。相似於HTTP 405。 【4.06】Not Acceptable 請求選項和服務器生成內容選項不一致。相似於HTTP 406。 【4.12】Precondition Failed 請求參數不足。相似於HTTP 412。 【4.15】Unsuppor Conten-Type 請求中的媒體類型不被支持。相似於HTTP 415。 【5.00】Internal Server Error 服務器內部錯誤。相似於HTTP 500。 【5.01】Not Implemented 服務器沒法支持請求內容。相似於HTTP 501。 【5.02】Bad Gateway 服務器做爲網關時,收到了一個錯誤的響應。相似於HTTP 502。 【5.03】Service Unavailable 服務器過載或者維護停機。相似於HTTP 503。 【5.04】Gateway Timeout 服務器做爲網關時,執行請求時發生超時錯誤。相似於HTTP 504。 【5.05】Proxying Not Supported 服務器不支持代理功能。
4. 驗證CoAP協議網關工做狀態
這裏有點須要強調,coap插件,我在V2的版本下,驗證沒有經過,一樣的客戶端程序(Sender),一樣的配置,broker採用emqx時,驗證經過。
發送端(java):
package com.taikang.iot.scc.research.coap; import org.eclipse.californium.core.CoapClient; import org.eclipse.californium.core.CoapResponse; import org.eclipse.californium.core.Utils; import org.eclipse.californium.core.coap.CoAP; import java.net.URI; import java.net.URISyntaxException; import java.util.Date; import java.util.Scanner; import static org.eclipse.californium.core.coap.MediaTypeRegistry.APPLICATION_OCTET_STREAM; import static org.eclipse.californium.core.coap.MediaTypeRegistry.TEXT_PLAIN; /** * @Author: chengsh05 * @Date: 2019/4/19 11:10 */ public class CoAPSender { public static void main(String[] args) throws URISyntaxException, InterruptedException { URI uri = new URI("coap://10.95.197.8:5683/mqtt/taikang/coapt?c=coaps1&u=water&p=water"); //建立一個資源請求taikang資源,注意默認端口爲5683 CoapClient client = new CoapClient(uri); // Scanner scan = new Scanner(System.in); // String inputChar = scan.nextLine(); while (true) { String payload = "hello, " + new Date().toString(); //將鍵盤輸入的payload初始化(非CoAP) //CoapResponse response = client.put(payload, TEXT_PLAIN);//設置PUT的內容和內容的類型TEXT_PLAIN //client.useCONs(); CoapResponse response = client.put(payload, APPLICATION_OCTET_STREAM);//設置PUT的內容和內容的類型APPLICATION_OCTET_STREAM // System.out.println(response.getCode()); // System.out.println(response.getOptions()); // System.out.println(response.getResponseText()); System.out.println(Utils.prettyPrint(response)); // inputChar = scan.nextLine(); Thread.sleep(5000); } } }
這裏,針對EMQ的CoAP協議使用,須要注意點事項:https://github.com/emqx/emqx-coap,這裏有較爲明確清晰的要求。
JAVA客戶端日誌:
12:02:58.644 [CoapEndpoint-UDP-0.0.0.0/0.0.0.0:0#1] DEBUG org.eclipse.californium.core.network.stack.ReliabilityLayer - Send request, failed transmissions: 0 12:02:58.644 [CoapEndpoint-UDP-0.0.0.0/0.0.0.0:0#1] DEBUG org.eclipse.californium.core.network.UdpMatcher - tracking open request [MID: 26804, Token: [e24d0df69f8a1071]] 12:02:58.644 [UDP-Sender-0.0.0.0/0.0.0.0:0[2]] DEBUG org.eclipse.californium.elements.UDPConnector - UDPConnector (Thread[UDP-Sender-0.0.0.0/0.0.0.0:0[2],5,Californium/Elements]) sends 94 bytes to /10.95.197.8:5683 12:02:58.652 [UDP-Receiver-0.0.0.0/0.0.0.0:0[3]] DEBUG org.eclipse.californium.elements.UDPConnector - UDPConnector (0.0.0.0/0.0.0.0:63536) received 12 bytes from /10.95.197.8:5683 12:02:58.652 [CoapEndpoint-UDP-0.0.0.0/0.0.0.0:0#1] DEBUG org.eclipse.californium.core.network.InMemoryMessageExchangeStore - removing exchange for MID KeyMID[26804, [0a5fc508]:5683] 12:02:58.652 [CoapEndpoint-UDP-0.0.0.0/0.0.0.0:0#1] DEBUG org.eclipse.californium.core.network.UdpMatcher - closed open request [KeyMID[26804, [0a5fc508]:5683]] 12:02:58.652 [CoapEndpoint-UDP-0.0.0.0/0.0.0.0:0#1] DEBUG org.eclipse.californium.core.network.InMemoryMessageExchangeStore - removing exchange for token Token[[e24d0df69f8a1071]] 12:02:58.652 [CoapEndpoint-UDP-0.0.0.0/0.0.0.0:0#1] DEBUG org.eclipse.californium.core.network.UdpMatcher - Exchange [Token[[e24d0df69f8a1071]], origin: LOCAL] completed ==[ CoAP Response ]============================================ MID : 26804 Token : [e24d0df69f8a1071] Type : ACK Status : 2.04 Options: {} RTT : 8 ms Payload: 0 Bytes
接收端(mosquitto工具基於mqtt協議接收,注意coap協議接收不到的,由於emqx_coap在emq裏面是個協議網關,將監聽到的coap協議數據轉化爲mqtt協議數據):
[root@ws2 ~]# mosquitto_sub -d -h 10.95.197.8 -p 1883 -t 'taikang/v3s' -i client21 -u shihuc -P shihuc Client client21 sending CONNECT Client client21 received CONNACK (0) Client client21 sending SUBSCRIBE (Mid: 1, Topic: taikang/v3s, QoS: 0) Client client21 received SUBACK Subscribed (mid: 1): 0 Client client21 received PUBLISH (d0, q0, r0, m0, 'taikang/v3s', ... (35 bytes)) hello, Thu Jun 06 10:20:56 CST 2019
這裏須要說明的是,基於mosquitto進行訂閱操做,topic的值,和EMQ的coap協議網關下的topic對應關係,須要仔細閱讀emqx_coap的官方文檔要求:
CoAP Client Publish Operation Issue a coap put command to do publishment. For example: PUT coap://localhost/mqtt/{topicname}?c={clientid}&u={username}&p={password}
"mqtt" in the path is mandatory. replace {topicname}, {clientid}, {username} and {password} with your true values. {topicname} and {clientid} is mandatory. if clientid is absent, a "bad_request" will be returned. {topicname} in URI should be percent-encoded to prevent special characters, such as + and #. {username} and {password} are optional. if {username} and {password} are not correct, an uauthorized error will be returned. payload could be any binary data. payload data type is "application/octet-stream". publish message will be sent with qos0.
注意:以前在V2.3.11的版本上操做coap協議的消息收發,遇到EMQ端老是爆出下面的錯誤:
當CoAP的服務端採用EMQTT,即V2版本(V2.3.11)時,服務端老是報錯,提示下面的錯誤: 11:03:00.538 [error] CoAP-RES: put has error, Prefix=[<<"mqtt">>], Name=[<<"taikang">>,<<"coapt">>], Content={coap_content,undefined,60,<<"application/octet-stream">>,[],<<"helloSun Apr 21 11:02:06 CST 2019">>} 11:03:05.554 [error] CoAP-RES: put has error, Prefix=[<<"mqtt">>], Name=[<<"taikang">>,<<"coapt">>], Content={coap_content,undefined,60,<<"application/octet-stream">>,[],<<"helloSun Apr 21 11:02:11 CST 2019">>} 11:03:10.564 [error] CoAP-RES: put has error, Prefix=[<<"mqtt">>], Name=[<<"taikang">>,<<"coapt">>], Content={coap_content,undefined,60,<<"application/octet-stream">>,[],<<"helloSun Apr 21 11:02:16 CST 2019">>} 11:03:15.574 [error] CoAP-RES: put has error, Prefix=[<<"mqtt">>], Name=[<<"taikang">>,<<"coapt">>], Content={coap_content,undefined,60,<<"application/octet-stream">>,[],<<"helloSun Apr 21 11:02:21 CST 2019">>}
查詢了不少資料(尤爲針對emq_coap的插件介紹說明),都麼有辦法解決,百度了不少,也找不到相關信息。最後懷疑是否是V2的版本有bug,索性換了V3的EMQ服務端(emqx-centos7-v3.0.1.x86_64.rpm),客戶端的java程序邏輯(URI地址IP不一樣,僅僅)不變,鏈接到V3版本一切正常。說明V2版本是有bug麼?針對這個問題,我對EMQ開源項目提了一個issue(https://github.com/emqx/emqx/issues/2468),有知道問題根源的同窗,也能夠給我留言,這個是我什麼地方搞錯了麼?
5. coaps的支持
在這個需求下,coap-client必須是有SSL支持的,這裏是openssl組件。若沒安裝SSL的組件,會遇到下面的錯誤:
[root@mq2 libcoap]# coap-client -m put -e "hahah, coap" -c /opt/certs/ecc/ecccrtkey.crt -C /opt/certs/ecc/eccRootCert.crt "coaps://10.95.197.8:5683/mqtt/taikang/rulee?c=coap001&u=water&p=water" Apr 25 11:11:31 EMRG coaps URI scheme not supported in this version of libcoap
這個須要libcoap的安裝的時候指定DTLS的支持,若不指定,且當前安裝libcoap的服務器上也沒有ssl相關的環境(openssl,或者gnutls等),那麼libcoap安裝後是不支持coaps協議的。
執行coap-client發佈coaps協議的消息:
[root@mq2 libcoap]# coap-client -m put -e "hahah, coap" -c /opt/certs/ecc/ecccrtkey.crt -C /opt/certs/ecc/eccRootCert.crt "coaps://10.95.197.8:5683/mqtt/taikang/rulee?c=coap001&u=water&p=water" coap-client: error while loading shared libraries: libssl.so.1.1: cannot open shared object file: No such file or directory
這個錯誤,參照前面提到的解決方案,這裏的信息主要是一個填坑備忘。
上述問題都解決了,發現coap-client進行coaps的通訊,仍是失敗(emqx的日誌中顯示下面的問題)。
unexpected massage {datagram,<<22,254,253,0,0,0,0,0,0,0,0,0,94,1,0,0,82,0,0,0, 0,0,0,0,82,254,253,92,193,89,246,123,189,1,34, 211,75,88,121,216,180,234,146,199,185,254,252, 84,234,1,17,156,163,161,148,128,226,178,127,0, 0,0,8,192,174,192,35,192,168,0,174,1,0,0,32,0, 10,0,8,0,6,0,23,0,24,0,25,0,11,0,2,1,0,0,19,0, 3,2,2,0,0,20,0,3,2,0,2>>}
這是第一次嘗試EMQ的coap協議網關功能,參考了EMQX的官方資料,仍是解決不了上述的coaps通訊問題,因而,嘗試本身在本地構建coap的服務端和coap的客戶端,基於java,並構建DTLS的通訊環境。
而後參照org.eclipse.californium(core, connector, scandium),參照https://github.com/eclipse/californium/tree/master/demo-apps/cf-secure/src/main/java/org/eclipse/californium/examples上面的DTLS通訊的例子,本身實現了一個客戶端和一個服務端,在本地機器上模擬coap的通訊。
客戶端程序:
/******************************************************************************* * Copyright (c) 2015 Institute for Pervasive Computing, ETH Zurich and others. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * and Eclipse Distribution License v1.0 which accompany this distribution. * * The Eclipse Public License is available at * http://www.eclipse.org/legal/epl-v10.html * and the Eclipse Distribution License is available at * http://www.eclipse.org/org/documents/edl-v10.html. * * Contributors: * Matthias Kovatsch - creator and main architect ******************************************************************************/ package com.taikang.iot.scc.research.coaps; import java.io.IOException; import java.io.InputStream; import java.net.InetSocketAddress; import java.net.URI; import java.net.URISyntaxException; import java.security.GeneralSecurityException; import java.security.KeyStore; import java.security.PrivateKey; import java.security.cert.Certificate; import java.security.cert.X509Certificate; import java.util.Date; import java.util.logging.Level; import com.taikang.iot.scc.research.security.SSLUtils; import org.eclipse.californium.core.CoapClient; import org.eclipse.californium.core.CoapResponse; import org.eclipse.californium.core.Utils; import org.eclipse.californium.core.network.CoapEndpoint; import org.eclipse.californium.core.network.config.NetworkConfig; import org.eclipse.californium.scandium.DTLSConnector; import org.eclipse.californium.scandium.ScandiumLogger; import org.eclipse.californium.scandium.config.DtlsConnectorConfig; import org.eclipse.californium.scandium.dtls.pskstore.StaticPskStore; import static org.eclipse.californium.core.coap.MediaTypeRegistry.APPLICATION_OCTET_STREAM; public class SecureClient { static { ScandiumLogger.initialize(); ScandiumLogger.setLevel(Level.FINE); } static String basePath = "E:\\2018\\IOT\\MQTT\\ssl\\"; //If you have modified GenKeys.sh modify the following variables accordingly private final static String clientCrt = basePath + "eccDevCert.crt"; private final static String clientKey = basePath + "eccDevCert.key"; private final static String serverCrt = basePath + "eccEmqCert.crt"; private final static String serverKey = basePath + "eccEmqCert.key"; private final static String coapsCA = basePath + "eccRootCert.crt"; // private static final String SERVER_URI = "coap://10.95.197.8:5683/mqtt/taikang/rulee?c=coaps007&u=water&p=water"; private static final String SERVER_URI = "coaps://10.95.177.137:5684/mqtt"; private DTLSConnector dtlsConnector; public SecureClient() { //Here starts DTLS configuration of the client //load the trust store PrivateKey cliKey = SSLUtils.loadPrivateKey(clientKey); X509Certificate cliCrt = SSLUtils.loadCertficate(clientCrt); // load trust store X509Certificate rootCrt = SSLUtils.loadCertficate(coapsCA); // You can load multiple certificates if needed Certificate[] trustedCertificates = new Certificate[1]; trustedCertificates[0] = rootCrt; DtlsConnectorConfig.Builder builder = new DtlsConnectorConfig.Builder(new InetSocketAddress(0)); builder.setPskStore(new StaticPskStore("Client_identity", "secretPSK".getBytes())); builder.setIdentity(cliKey, new Certificate[]{cliCrt}, true); builder.setTrustStore(trustedCertificates); dtlsConnector = new DTLSConnector(builder.build()); } public void test() { CoapResponse response = null; try { URI uri = new URI(SERVER_URI); CoapClient client = new CoapClient(uri); client.setEndpoint(new CoapEndpoint(dtlsConnector, NetworkConfig.getStandard())); while (true) { String payload = "hello, " + new Date().toString(); //將鍵盤輸入的payload初始化(非CoAP) response = client.put(payload, APPLICATION_OCTET_STREAM);//設置PUT的內容和內容的類型APPLICATION_OCTET_STREAM if(response != null) { System.out.println(Utils.prettyPrint(response)); }else { System.out.println("there is no response for this put operation"); } // response = client.get(); // System.out.println(Utils.prettyPrint(response)); Thread.sleep(5000); } } catch (URISyntaxException e) { System.err.println("Invalid URI: " + e.getMessage()); System.exit(-1); } catch (InterruptedException e) { e.printStackTrace(); } if (response != null) { // // System.out.println(response.getCode()); // System.out.println(response.getOptions()); // System.out.println(response.getResponseText()); // System.out.println("\nADVANCED\n"); System.out.println(Utils.prettyPrint(response)); } else { System.out.println("No response received."); } } public static void main(String[] args) throws InterruptedException { SecureClient client = new SecureClient(); client.test(); synchronized (SecureClient.class) { SecureClient.class.wait(); } } }
服務端程序:
/******************************************************************************* * Copyright (c) 2015 Institute for Pervasive Computing, ETH Zurich and others. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * and Eclipse Distribution License v1.0 which accompany this distribution. * * The Eclipse Public License is available at * http://www.eclipse.org/legal/epl-v10.html * and the Eclipse Distribution License is available at * http://www.eclipse.org/org/documents/edl-v10.html. * * Contributors: * Matthias Kovatsch - creator and main architect ******************************************************************************/ package com.taikang.iot.scc.research.coaps; import java.net.InetSocketAddress; import java.security.PrivateKey; import java.security.cert.Certificate; import java.security.cert.X509Certificate; import java.util.Date; import java.util.logging.Level; import com.taikang.iot.scc.research.security.SSLUtils; import org.eclipse.californium.core.CaliforniumLogger; import org.eclipse.californium.core.CoapResource; import org.eclipse.californium.core.CoapServer; import org.eclipse.californium.core.coap.CoAP.ResponseCode; import org.eclipse.californium.core.network.CoapEndpoint; import org.eclipse.californium.core.network.Endpoint; import org.eclipse.californium.core.network.config.NetworkConfig; import org.eclipse.californium.core.network.interceptors.MessageTracer; import org.eclipse.californium.core.server.resources.CoapExchange; import org.eclipse.californium.scandium.DTLSConnector; import org.eclipse.californium.scandium.ScandiumLogger; import org.eclipse.californium.scandium.config.DtlsConnectorConfig; import org.eclipse.californium.scandium.dtls.cipher.CipherSuite; public class SecureServer { static { CaliforniumLogger.initialize(); CaliforniumLogger.setLevel(Level.CONFIG); ScandiumLogger.initialize(); ScandiumLogger.setLevel(Level.FINER); } // allows configuration via Californium.properties public static final int DTLS_PORT = NetworkConfig.getStandard().getInt(NetworkConfig.Keys.COAP_SECURE_PORT); static String basePath = "E:\\2018\\IOT\\MQTT\\ssl\\"; //If you have modified GenKeys.sh modify the following variables accordingly private final static String clientCrt = basePath + "eccDevCert.crt"; private final static String clientKey = basePath + "eccDevCert.key"; private final static String serverCrt = basePath + "eccEmqCert.crt"; private final static String serverKey = basePath + "eccEmqCert.key"; private final static String coapsCA = basePath + "eccRootCert.crt"; public static void main(String[] args) { CoapServer server = new CoapServer(); server.add(new CoapResource("hello") { @Override public void handleGET(CoapExchange exchange) { exchange.respond(ResponseCode.CONTENT, "handleGET==>hello," + new Date().toString()); } }); server.add(new CoapResource("mqtt") { @Override public void handlePUT(CoapExchange exchange) { exchange.respond(ResponseCode.CONTENT, "handlePUT==>mqtt," + new Date().toString()); } }); server.add(new CoapResource("coap") { @Override public void handlePOST(CoapExchange exchange) { exchange.respond(ResponseCode.CONTENT, "handlePOST==>coap," + new Date().toString()); } }); // Pre-shared secrets //Here starts DTLS configuration of the client //load the trust store PrivateKey svrKey = SSLUtils.loadPrivateDERKey(serverKey); X509Certificate svrCrt = SSLUtils.loadCertficate(serverCrt); // load trust store X509Certificate rootCrt = SSLUtils.loadCertficate(coapsCA); // You can load multiple certificates if needed Certificate[] trustedCertificates = new Certificate[1]; trustedCertificates[0] = rootCrt; DtlsConnectorConfig.Builder config = new DtlsConnectorConfig.Builder(new InetSocketAddress(DTLS_PORT)); config.setSupportedCipherSuites(new CipherSuite[]{ CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8, CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8}); config.setIdentity(svrKey, new Certificate[]{svrCrt}, true); config.setTrustStore(trustedCertificates); //默認狀況下,服務端是要對客戶端的安全要作驗證的(即所謂的雙向驗證) config.setClientAuthenticationRequired(false); DTLSConnector connector = new DTLSConnector(config.build()); server.addEndpoint(new CoapEndpoint(connector, NetworkConfig.getStandard())); server.start(); // add special interceptor for message traces for (Endpoint ep : server.getEndpoints()) { ep.addInterceptor(new MessageTracer()); } System.out.println("Secure CoAP server powered by Scandium (Sc) is listening on port " + DTLS_PORT); } }
PS:
1. 用java的secureServer做爲server,而後用coap-client做爲client進行測試,驗證邏輯, coap-client的日誌以下:
[root@mq2 ecc]# coap-client -t binary -c /opt/certs/ecc/ecccrtkey.crt -C /opt/certs/ecc/eccRootCert.crt -m get "coaps://10.95.177.137/hello" Apr 28 12:24:47 WARN 10.95.197.8:33118 <-> 10.95.177.137:5684 DTLS: unable to get certificate CRL: overridden: '10.95.197.8' depth=0 handleGET==>hello,Sun Apr 28 12:28:41 CST 2019 [root@mq2 ecc]# [root@mq2 ecc]# coap-client -t binary -c /opt/certs/ecc/ecccrtkey.crt -C /opt/certs/ecc/eccRootCert.crt -e "hello mqtt" -m put "coaps://10.95.177.137/mqtt" Apr 28 12:25:10 WARN 10.95.197.8:38407 <-> 10.95.177.137:5684 DTLS: unable to get certificate CRL: overridden: '10.95.197.8' depth=0 handlePUT==>mqtt,Sun Apr 28 12:29:05 CST 2019 [root@mq2 ecc]# [root@mq2 ecc]# coap-client -t binary -c /opt/certs/ecc/ecccrtkey.crt -C /opt/certs/ecc/eccRootCert.crt -e "hello mqtt with coaps" -m post "coaps://10.95.177.137/coap" Apr 28 12:25:33 WARN 10.95.197.8:43010 <-> 10.95.177.137:5684 DTLS: unable to get certificate CRL: overridden: '10.95.197.8' depth=0 handlePOST==>coap,Sun Apr 28 12:29:27 CST 2019
注意:服務端的COAPResource的path部分填寫,即上面代碼中的hello, mqtt, coap, 這些針對不一樣的method(hello:get, mqtt:put, coap:post),不能出現同一個path,對應不一樣的method,就相似同一個主題或者URL,即有發佈又有訂閱,是不容許的,程序執行時,client端會報4.05的錯誤.
2.coap-client的指令使用說明中關於coaps配置(COAP協議,爲了保證傳輸信息量儘可能下,證書算法支持ECC和PSK兩種,其餘的彷佛不支持【至少在california的工具包裏面是限制了】),有下面的說明:
PSK Options (if supported by underlying (D)TLS library) -k key Pre-shared key for the specified user -u user User identity for pre-shared key mode PKI Options (if supported by underlying (D)TLS library) -c certfile PEM file containing both CERTIFICATE and PRIVATE KEY This argument requires (D)TLS with PKI to be available -C cafile PEM file containing the CA Certificate that was used to sign the certfile. This will trigger the validation of the server certificate. If certfile is self-signed (as defined by '-c certfile'), then you need to have on the command line the same filename for both the certfile and cafile (as in '-c certfile -C certfile') to trigger validation -R root_cafile PEM file containing the set of trusted root CAs that are to be used to validate the server certificate. The '-C cafile' does not have to be in this list and is 'trusted' for the verification. Alternatively, this can point to a directory containing a set of CA PEM files
上述的個人案例中,用的是PKI的模式,採用ECC的證書。參數-c certfile說明要求,證書和私鑰要在一個配置文件中,我這裏將私鑰append到證書的後面了,本案例中,ecccrtkey.crt文件以下:
-----BEGIN CERTIFICATE----- MIICDzCCAbOgAwIBAgIEXMAt2jAMBggqhkjOPQQDAgUAMGYxEzARBgNVBAMMCklPVF9FQ0NfQ0Ex CzAJBgNVBAYTAkNOMQ4wDAYDVQQIEwVIdWJlaTEOMAwGA1UEBxMFV3VoYW4xEDAOBgNVBAoTB1RL Q2xvdWQxEDAOBgNVBAsTB1RhaUthbmcwHhcNMTkwNDI0MDkzNTIyWhcNMjAwNDIzMDkzNTIyWjBm MRMwEQYDVQQDDApJT1RfRGV2aWNlMQswCQYDVQQGEwJDTjEOMAwGA1UECBMFSHViZWkxDjAMBgNV BAcTBVd1aGFuMRAwDgYDVQQKEwdUS0Nsb3VkMRAwDgYDVQQLEwdUYWlLYW5nMFkwEwYHKoZIzj0C AQYIKoZIzj0DAQcDQgAEcQVnG7L5k0YqSYnw+DFc4FjFfdKsBK28AYQ4uOnzzHxHRQNgJZqMHFYO abMWpmgUjhg2akpHf5xQOPEiLGXl/aNNMEswHwYDVR0jBBgwFoAUp1pH8oPTujZTqsR5cPYf0m3T DxQwCQYDVR0TBAIwADAdBgNVHQ4EFgQU3I9TpzP9ohiYyqy15fdSBlSLdrAwDAYIKoZIzj0EAwIF AANIADBFAiEAlrtKf38SF05Pm48GMirVVnqkUli/YDRE51+SHVvgSq0CIBPrrIw4/51XRpC19ml6 iPwF4adyy5+QTU1cSVXmv6KS -----END CERTIFICATE----- -----BEGIN PRIVATE KEY----- MEECAQAwEwYHKoZIzj0CAQYIKoZIzj0DAQcEJzAlAgEBBCDnfo/KSIXSc9/8CR8B zEjgIpem2rty55ReGShwUGp0sg== -----END PRIVATE KEY-----
這裏的-C cafile的描述,指的就是簽發證書,我這裏就是自簽名證書。指令中麼有使用-R這個選項。
JAVA的模擬COAPS的通訊是正常的,沒有任何問題。可是在emqx的環境下驗證coaps,驗證通訊是失敗的,初步得出結論:
1. EC橢圓曲線算法生成的證書是沒有問題的。
2. COAPS的通訊邏輯是基本走通了,沒有問題的。
3. EMQX的coaps通訊邏輯目前應該是有問題的,或者是基本沒有作支持。
針對這個EMQX對COAPS的支持,我也向EMQ團隊提出了ISSUE(https://github.com/emqx/emqx-docs-cn/issues/136)這個,能夠說明EMQ目前在快速迭代,裏面也存在一些問題,須要開發者和應用者及時驗證和反饋,否則,應用極可能會出現問題,本身都沒法知道根源。
好了,到此,有什麼須要探討的,能夠關注個人博客,一塊兒交流。