2021年7月

“欲速则不达,见小利则大事不成。”

使用tls 双向认证时,需要提供客户端的证书。在mosquitto的测试服务器,有提供客户端证书的签发功能。需要自己实现证书签名请求。
操作如下:
1.生成rsa私钥

openssl genrsa -out mosquitto-client.key 2048

Generating RSA private key, 2048 bit long modulus (2 primes)
..+++++
........................+++++
e is 65537 (0x010001)
2.生成客户端证书签名请求

openssl req -config ../ssl/openssl.cnf -out mosquitto-client.csr -key mosquitto-client.key -new

You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.

Country Name (2 letter code) [AU]:CN
State or Province Name (full name) [Some-State]:GD
Locality Name (eg, city) []:GZ
Organization Name (eg, company) [Internet Widgits Pty Ltd]:GV
Organizational Unit Name (eg, section) []:RD
Common Name (e.g. server FQDN or YOUR name) []:const.net.cn
Email Address []:admin@const.net.cn

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:

3.Generate a TLS client certificate for test.mosquitto.org
访问网址:https://test.mosquitto.org/ssl/ 将csr粘贴到网站下,下载客户端证书。保存为mosquitto-client.crt.
生成客户端证书网页如图
mosquitto-client-crt-request.png

下面开始测试mqtt 双向认证测试工作
发送订阅命令:

mosquitto_sub -L "mqtts://test.mosquitto.org:8884/const.net.cn/" --cert mosquitto-client.crt --key mosquitto-client.key --cafile ../mosquitto.org.crt -d

Client (null) sending CONNECT
Client (null) received CONNACK (0)
Client (null) sending SUBSCRIBE (Mid: 1, Topic: const.net.cn/, QoS: 0, Options: 0x00)
Client (null) received SUBACK
Subscribed (mid: 1): 0
带Qos参数的订阅命令:

mosquitto_sub -L "mqtts://test.mosquitto.org:8884/const.net.cn/" --cert mosquitto-client.crt --key mosquitto-client.key --cafile ../mosquto.org.crt -q 2 -d

Client (null) sending CONNECT
Client (null) received CONNACK (0)
Client (null) sending SUBSCRIBE (Mid: 1, Topic: const.net.cn/, QoS: 2, Options: 0x00)
Client (null) received SUBACK
Subscribed (mid: 1): 2

注意,需要带上客户端证书,客户端私钥,还有ca证书,否则,可能会得到以下错误提示
OpenSSL Error[0]: error:1416F086:SSL routines:tls_process_server_certificate:certificate verify failed
Error: A TLS error occurred.

发布命令:

mosquitto_pub -L "mqtts://test.mosquitto.org:8884/const.net.cn/" --cert mosquitto-client.crt --key mosquitto-client.key --cafile ../mosquitto.org.crt -m 'mqtt example 8884 : MQTT, encrypted, client certificate required' -d

Client (null) sending CONNECT
Client (null) received CONNACK (0)
Client (null) sending PUBLISH (d0, q0, r0, m1, 'const.net.cn/', ... (64 bytes))
Client (null) sending DISCONNECT

mosquitto_sub接收到的数据
Client (null) received PUBLISH (d0, q0, r0, m0, 'const.net.cn/', ... (64 bytes))
mqtt example 8884 : MQTT, encrypted, client certificate required
Client (null) sending PINGREQ
Client (null) received PINGRESP

8885 : MQTT, encrypted, authenticated

需要用户名,密码,和ca证书。

订阅命令:

mosquitto_sub -L "mqtts://test.mosquitto.org:8885/const.net.cn" --cafile ../mosquitto.org.crt -u rw -P readwrite -d

Client (null) sending CONNECT
Client (null) received CONNACK (0)
Client (null) sending SUBSCRIBE (Mid: 1, Topic: const.net.cn, QoS: 0, Options: 0x00)
Client (null) received SUBACK
Subscribed (mid: 1): 0
带Qos的订阅命令:

mosquitto_sub -L "mqtts://test.mosquitto.org:8885/const.net.cn/" --cafile ../mosquitto.org.crt -u rw -P readwrite -q 2 -d

Client (null) sending CONNECT
Client (null) received CONNACK (0)
Client (null) sending SUBSCRIBE (Mid: 1, Topic: const.net.cn/, QoS: 2, Options: 0x00)
Client (null) received SUBACK
Subscribed (mid: 1): 2

发布命令:

mosquitto_pub -L "mqtts://test.mosquitto.org:8885/const.net.cn/" --cafile ../mosquitto.org.crt -u rw -P readwrite -m 'mqtt example 8885 : MQTT, encrypted, authenticated' -d

Client (null) sending CONNECT
Client (null) received CONNACK (0)
Client (null) sending PUBLISH (d0, q0, r0, m1, 'const.net.cn/', ... (50 bytes))
Client (null) sending DISCONNECT

接收到的数据

Client (null) received PUBLISH (d0, q0, r0, m0, 'const.net.cn/', ... (50 bytes))
mqtt example 8885 : MQTT, encrypted, authenticated

测试过程中,出现了一点问题,已经设置了Qos为2,却怎么也收不到mqtt信息,最后发现topic少了一个斜杠,发送为const.net.cn/,接收/订阅为const.net.cn,怎么也收不到信息。。怀疑人生。。。。

订阅#可让您订阅除$之外的所有主题的订阅
当然,最好先了解您订阅的内容,并注意某些代理配置可能会禁止明确订阅#。
您可以使用mosquitto_sub(这是mosquitto-clients包的一部分)并订阅通配符主题#:

mosquitto_sub -v -h test.mosquitto.org -p 1883 -t '#'

通配符是一个匹配主题中任意层次数的通配符。比如说,如果你订阅了finance/stock/ibm/#,你就可以接收到以下这些主题的消息。
finance/stock/ibm
finance/stock/ibm/closingprice
finance/stock/ibm/currentprice
多层通配符有可以表示大于等于0的层次。因此,finance/#也可以匹配到单独的finance,在这种情况下#代表0层。在这种语境下主题层次分隔符/就没有意义了。因为没有可以分的层次。

多层通配符只可以确定当前层或者下一层。因此,#和finance/#都是有效的,但是finance#不是有效的。多层通配符一定要是主题树的最后一个字符。比如说,finance/#是有效的,但是finance/#/closingprice是无效的。

通配符+只匹配主题的一层。比如说,finance/stock/+匹配finance/stock/ibm和finance/stock/xyz,但是不匹配finance/stock/ibm/closingprice。另外,因为单层通配符只匹配1层,finance/+不匹配finance。
单层通配符可以被用于主题树的任意层级,连带多层通配符。它必须被用在主题层级分隔符/的右边,除非它是指定自己。因此,+和finance/+都是有效的,但是finance+无效。单层通配符可以用在主题树的末端,也可以用在中间。比如说,finance/+和finance/+/ibm都是有效的。

topic定义约束
topic至少有一个字符。
topic名字是大小写敏感的。比如说,ACCOUNTS和Accounts是两个不同的主题。
topic名字可以包含空格。比如,Accounts payable是一个有效的主题。
以/开头会产生一个不同的主题。比如说,/finnace与finance不同。/finance匹配"+/+"和/+,但不匹配+
不要在任何主题中包含null(Unicode x0000)字符。
topic树中,长度不能超过64k,层数不限。

mosquitto deny # wildcards
mosquitto的解决思路是,白名单的方式,使用acl使指定的用户,只能够订阅指定的主题,就可以禁止订阅#了。

emq 禁止所有用户订阅#

拒绝用户订阅'$SYS#'与'#'主题

{deny, all, subscribe, ["$SYS/#", {eq, "#"}]}

EMQ mqtt相关acl示例

%% 允许 "dashboard" 用户 订阅 "$SYS/#" 主题
{allow, {user, "dashboard"}, subscribe, ["$SYS/#"]}.

%% 允许 IP 地址为 "127.0.0.1" 的用户 发布/订阅 "$SYS/#","#" 主题
{allow, {ipaddr, "127.0.0.1"}, pubsub, ["$SYS/#", "#"]}.

%% 拒绝 "所有用户" 订阅 "$SYS/#" "#" 主题
{deny, all, subscribe, ["$SYS/#", {eq, "#"}]}.

%% 允许其它任意的发布订阅操作
{allow, all}.

mosquitto-tls — Configure SSL/TLS support for Mosquitto

配置说明
mosquitto 为加密的网络连接和身份验证提供 SSL 支持。本手册描述了如何创建所需的文件。

注意事项
为您的 CA、服务器和客户端使用不同的证书参数很重要。如果证书看起来相同,即使是单独生成的,代理/客户端也无法区分它们,您将遇到难以诊断的错误。

生成证书
以下部分提供了可用于生成证书的 openssl 命令,但没有任何上下文。 https://asciinema.org/a/201826 上的 asciicast 完整介绍了如何使用这些命令。英文操作视频。基于linux bash 操作的。

证书颁发机构
生成证书颁发机构证书和密钥。

openssl req -new -x509 -days <duration> -extensions v3_ca -keyout ca.key -out ca.crt

服务器端
生成服务器密钥(采用des3加密保护)。

openssl genrsa -des3 -out server.key 2048

生成无密码保护的服务器密钥。

openssl genrsa -out server.key 2048

生成要发送给 CA 的证书签名请求。

openssl req -out server.csr -key server.key -new

注意事项:
当提示输入 CN(通用名称)时,请输入您的服务器(或代理)主机名或域名。

将 CSR 发送给 CA,或使用您的 CA 密钥对其进行签名:

openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt -days <duration>

用户端
生成客户端密钥。

openssl genrsa -des3 -out client.key 2048

生成要发送给 CA 的证书签名请求。

openssl req -out client.csr -key client.key -new

将 CSR 发送给 CA,或使用您的 CA 密钥对其进行签名:

openssl x509 -req -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out client.crt -days <duration>