MQTT研究之EMQ:【CoAP協議應用開發】


本博文的重點是嘗試CoAP協議的應用開發,其中包含CoAP協議中一個重要的開源工具libcoap的安裝和遇到的問題調研。當然,為了很好的將EMQ的CoAP協議網關用起來,也調研了下EMQ體系下,CoAP的使用邏輯, CoAP支持明文,也支持DTLS的安全傳輸。

 

首先,介紹下libcoap的環境准備,然后基於libcoap進行EMQ的CoAP協議支持的驗證。我的環境信息如下:

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/Linux

2. libcoap: 4.1.1

3. EMQ:起初10.95.200.11上是EMQ v2.3.11,后來驗證coap無法正常工作,將EMQ的V3版本即emqx v3.0.1在10.95.197.8上安裝,再次驗證coap工作。

為了驗證后面的COAPS通信,即CoAP基於DTLS的安全通信,這里,將libcoap的SSL環境也做一下准備,libcoap支持多種SSL的組件,這里,選擇最為基礎的且是最為常用的組件openssl。

 

1. libcoap帶安全組件的環境構建

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目錄下。

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目前在快速迭代,里面也存在一些問題,需要開發者和應用者及時驗證和反饋,不然,應用很可能會出現問題,自己都無法知道根源。

 

好了,到此,有什么需要探討的,可以關注我的博客,一起交流。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM