正文
在使用 mosquitto 对 MQTT 开启 TLS 进行测试时,经常会遇到各种神奇的错误,但是 mosquitto 的日志却少的可怜,服务端能看到的日志大约就这几种:
SSL routines:ssl3_read_bytes:tlsv1 alert unknown ca
SSL routines:ssl3_read_bytes:ssl handshake failure
Socket error on client <unknown>, disconnecting.
客户端能看到的日志也少得可怜:
Error: A TLS error occurred.
下面总结几种常见的错误:
- 使用自签名证书时,CA证书 和 server证书 的 Comon Name 使用了相同的内容;这样会导致 OpenSSL 校验证书时失败,将 CA证书 和 server证书的 Comon Name 改成不同的内容即可;
- 使用自签名证书时,server证书 的 Comon Name 与域名不相符,默认情况下客户端会连接错误,这时在连接时加入
--insecure
参数即可; - 使用自签名证书时,如果CA是单个文件,将
--cafile
参数错写成--capath
; - 服务端开启双向认证
require_certificate true
, 连接时没有传入客户端的证书和**; - 服务端与客户端的 TLS 版本不一致,服务端配置参数为
tls_version
,客户端配置参数为--tls-version
;
当然,mosquitto 的日志不详细时,需要使用其他更有效的方式来诊断问题,这里提供一种有效的方式,使用 wireshark 来过滤底层通信数据,下面是使用 wireshark 的截图:
TLS Record 有四种类型:
- change_cipher_spec (20)
- alert (21)
- handshake (22)
- application_data (23)
通常在出现错误时都会通过 alert 消息来返回错误信息,所以可以通过 Alert 消息来诊断问题。上图中的 Alert 消息可以看出是CA证书 出了问题,这时可以检查 CA证书是否一致,是否加载成功等,然后找到问题并解决。
RFC5246 对 Alert 定义
/* https://tools.ietf.org/html/rfc5246#page-28 */
enum { warning(1), fatal(2), (255) } AlertLevel;
enum {
close_notify(0),
unexpected_message(10),
bad_record_mac(20),
decryption_failed_RESERVED(21),
record_overflow(22),
decompression_failure(30),
handshake_failure(40),
no_certificate_RESERVED(41),
bad_certificate(42),
unsupported_certificate(43),
certificate_revoked(44),
certificate_expired(45),
certificate_unknown(46),
illegal_parameter(47),
unknown_ca(48),
access_denied(49),
decode_error(50),
decrypt_error(51),
export_restriction_RESERVED(60),
protocol_version(70),
insufficient_security(71),
internal_error(80),
user_canceled(90),
no_renegotiation(100),
unsupported_extension(110),
(255)
} AlertDescription;
struct {
AlertLevel level;
AlertDescription description;
} Alert;
生成证书脚本
#!/usr/bin/env bash
# When OpenSSL prompts you for the Common Name for each certificate, use different names.
# CA key
openssl genrsa -out ca.key 2048
# CA csr
openssl req -new -subj "/CN=ca" -key ca.key -out ca.csr
# CA crt
openssl x509 -req -in ca.csr -out ca.crt -signkey ca.key -days 3650
# server key
openssl genrsa -out server.key 2048
# server.csr
openssl req -new -subj "/CN=server" -key server.key -out server.csr
# server.crt
openssl x509 -req -in server.csr -out server.crt -CA ca.crt -CAkey ca.key \
-CAcreateserial -days 3650
# server.crt verify
openssl verify -CAfile ca.crt server.crt
# client key
openssl genrsa -out client.key 2048
# client.csr
openssl req -new -subj "/CN=client" -key client.key -out client.csr
# client.crt
openssl x509 -req -in client.csr -out client.crt -CA ca.crt -CAkey ca.key \
-CAcreateserial -days 3650
# client.crt verify
openssl verify -CAfile ca.crt client.crt
配置文件
# Place your local configuration in /etc/mosquitto/conf.d/
#
# A full description of the configuration file is at
# /usr/share/doc/mosquitto/examples/mosquitto.conf.example
pid_file /var/run/mosquitto.pid
port 1883
persistence true
persistence_location /var/lib/mosquitto/
log_dest file /var/log/mosquitto/mosquitto.log
allow_anonymous false
password_file /root/mosquitto/pwfile
# acl_file /root/mosquitto/aclfile
listener 8883
cafile ca.crt
certfile server.crt
keyfile server.key
# one-way or two-way authentication
require_certificate true
生成用户名密码配置文件
# create pwfile and add user
mosquitto_passwd -c pwfile admin
# add user on pwfile with password
mosquitto_passwd -b pwfile admin admin
# add user on pwfile
mosquitto_passwd pwfile admin
测试命令
# insecure sub
mosquitto_sub -d \
-k 30 \
-h localhost -p 1883 \
-t gateway/test0 \
-u admin -P admin
# insecure pub
mosquitto_pub -d \
-h localhost -p 1883 \
-t gateway/test0 \
-u admin -P admin \
-m {"message":"THIS_IS_A_TEST_MESSAGE"}
# secure pub one-way and no-check CA common name
mosquitto_pub -d \
-h localhost -p 8883 \
-t gateway/test0 \
-u admin -P admin \
--cafile ca.crt \
--insecure \
-m {"message":"THIS_IS_A_TEST_MESSAGE"}
# secure pub two-way
mosquitto_pub -d \
-h localhost -p 8883 \
-t gateway/test0 \
-u admin -P admin \
--cafile ca.crt \
--cert client.crt --key client.key \
-m {"message":"THIS_IS_A_TEST_MESSAGE"}