本博文的重点是尝试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目前在快速迭代,里面也存在一些问题,须要开发者和应用者及时验证和反馈,否则,应用极可能会出现问题,本身都没法知道根源。
好了,到此,有什么须要探讨的,能够关注个人博客,一块儿交流。