简介
在前面章节讲解了MQTT基本协议以及C语言如何使用libmosquitto库,在实际开发中,我们往往还需要对MQTT交互数据进行加密,MQTT支持设置SSL加密处理,最近在开发中也加入SSL支持,但是遇到很多坑,这里做个总结。
测试环境
- ubuntu云服务器一台(也可以本地测试)
- OpenWrt路由器一台,支持mosquitto-ssl
服务器环境搭建
通过apt命令安装mosquitto
apt install mosquitto
mosquitto是一个常用的MQTT broker,可以直接安装,方便测试。
安装后需要修改mosquitto配置文件/etc/mosquitto/mosquitto.conf
以下为示例配置:
pid_file /var/run/mosquitto.pid
persistence true
persistence_location /var/lib/mosquitto/
log_dest file /var/log/mosquitto/mosquitto.log
include_dir /etc/mosquitto/conf.d
cafile /etc/mosquitto/ca_certificates/ca.crt
certfile /etc/mosquitto/ca_certificates/server.crt
keyfile /etc/mosquitto/ca_certificates/server.key
#require_certificate true
#tls_version tlsv1.2
allow_anonymous false
password_file /etc/mosquitto/pwfile
port 1883
如果要支持ssl加密,需要设置cafile、certfile、keyfile
require_certificate可以不用设置,根据自己需求来配置,如果遇到问题可以开启和关闭该选项试试。
tls_version也不需要强制指定,如果指定了版本,可能导致协商失败,不配置也是可以连接的。
password_file是通用配置,不采用ssl加密也需要设置用户名密码,关于用户名密码配置不再赘述。
端口号默认1883,注意如果是云服务器,需要在系统防火墙放行该端口。
证书生成
ssl加密需要证书,首先我们需要通过openssl命令生成,证书包含服务器证书、客户端证书、公钥,服务器和客户端要分别配置,以上为服务器的证书配置文件。
以下为openssl创建证书脚本,具体命令参考脚本内容,注意把脚本内容复制后新建对应文件。
辅助脚本
- v3.ext
引用的参数,名称必须为v3.ext,脚本中会用到该文件
文件内容
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer:always
basicConstraints = CA:TRUE
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment, keyAgreement, keyCertSign
subjectAltName = DNS:mint2.home, DNS:mint3.home,DNS:mint4.home
issuerAltName = issuer:copy
- server-certs.sh
生成服务器证书和公钥
文件内容
dir="server-certs"
FILE="v3.ext"
echo "Creating CA key The passkey is important so remember it"
openssl genrsa -des3 -out ca.key 2048
echo "Created CA Certificate 10 years expiry"
openssl req -new -x509 -days 3650 -key ca.key -out ca.crt
echo "Creating Server key"
openssl genrsa -out server.key 2048
echo "Creating Server certificate"
openssl req -new -out server.csr -key server.key
echo "Signing Server certificate Server certificate"
echo "Common Name should be the FQDN or IP address of the server"
echo "It is what you would use to ping the server"
if [ -f "$FILE" ];then
echo "$FILE exists"
break
else
echo "You need an external file called $FILE to continue"
fi
echo "Press enter to continue"
read var1
if [ ! -f "$FILE" ];then
exit 1
fi
openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt -days 3650 -extfile $FILE
echo "copying files to certs subdirectory"
if [ -d $dir ]
then
echo "directory Exists"
else
mkdir $dir
fi
mv *.crt $dir
mv *.key $dir
mv *.csr $dir
mv *.srl $dir
- client-certs.sh
文件内容
#!/bin/bash
set -x
#doesn't currently work using variables
cacert = 'server-certs/ca.crt'
cakey = 'server-certs/ca.key'
dir="client-certs"
echo "Creating Client Key"
openssl genrsa -out client.key 2048
echo "Creating certificate request"
openssl req -new -out client.csr -key client.key
echo "Signing client certificate with CA key"
echo "The CA key File must be in the server-certs folder"
openssl x509 -req -in client.csr -CA server-certs/ca.crt -CAkey server-certs/ca.key -CAcreateserial -out client.crt -days 3650
if [ -d $dir ];then
echo "directory Exists"
else
mkdir $dir
fi
mv client.* $dir
运行脚本以及参数介绍
- 运行
server-certs.sh
脚本
-----
Country Name (2 letter code) [AU]:CN
State or Province Name (full name) [Some-State]:
Locality Name (eg, city) []:
Organization Name (eg, company) [Internet Widgits Pty Ltd]:
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:fros
Email Address []:
Creating Server key
Country Name输入CN,Common Name设置为hostname或者服务器ip,也可以输入随便的字符,比如test。其他的参数非必填,直接回车即可。
Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:
+ echo 'Signing client certificate with CA key'
Signing client certificate with CA key
+ echo 'The CA key File must be in the server-certs folder'
The CA key File must be in the server-certs folder
+ openssl x509 -req -in client.csr -CA server-certs/ca.crt -CAkey server-certs/ca.key -CAcreateserial -out client.crt -days 3650
Signature ok
subject=C = CN, ST = Some-State, O = Internet Widgits Pty Ltd, CN = fros
Getting CA Private Key
Enter pass phrase for server-certs/ca.key:
在这一步会提示输入密码Enter pass phrase
,自己设置一个密码即可,其他的默认回车。
按照提示运行完脚本后会生成server-certs
目录,目录中包含了公钥和服务器证书
root@test:~/certs# ls server-certs
ca.crt ca.key ca.srl server.crt server.csr server.key
- 运行
client-certs.sh
脚本
和服务器证书生成方式一样,运行脚本,按照服务器脚本参数一样输入,最后可以生成客户端证书。
root@test:~/certs# ls client-certs
client.crt client.csr client.key
证书部署
- 服务器
将server-certs目录中的文件拷贝到服务器指定目录,和mosquitto配置文件中的一致就可以,配置文件和证书配置好后重启mosquitto服务
/etc/init.d/mosquitto restart
- 客户端
客户端在OpenWrt系统中,需要将server-certs中的ca.crt和client-certs目录中的所有文件上传到OpenWrt系统中,注意后面会用到证书文件路径。
命令行测试
在用代码实现客户端之前,可以先用自带的mosquitto_sub和mosquitto_pub命令测试,在OpenWrt中注意把mosquitto_client编译进去。
以下为示例命令:
mosquitto_sub -d -v -h 127.0.0.1 -p 1883 -t test -u test -P 123456 --cafile /etc/mqtt_ssl/ca.crt --cert /etc/mqtt_ssl/client.crt --key /etc/mqtt_ssl/client.key --insecure
如果是跨网络连接,请将127.0.0.1修改为对应IP地址-u
: 用户名,注意ssl加密也是要输入用户名密码的-P
: 密码--insecure
: 不校验hostname,这里一般是必须填,除非前面生成证书时指定了正确的域名或hostname,但这里没必要限制。 证书参数
: 指定公钥和证书路径,比如–cafile、–cert、–key,名称和脚本生成的文件名一致即可。
调用以上命令后没有报错表示成功。
C语言实现MQTT SSL连接
和非SSL调用库类似,只需要加入以下接口调用即可
mosquitto_tls_insecure_set(mosq, true);
rc = mosquitto_tls_set(mosq, "/etc/mqtt_ssl/ca.crt", "/etc/mqtt_ssl/",
"/etc/mqtt_ssl/client.crt", "/etc/mqtt_ssl/client.key", NULL);
if (rc != MOSQ_ERR_SUCCESS){
printf("set tls error...\n");
}
注意mosquitto_tls_opts_set
是可以不用设置的,因为服务器报错版本问题可能误导去设置版本号。
在调试过程中,服务器出现以下错误,看日志认为是ssl版本问题,实际上是因为mosquitto_tls_set设置参数错误引起,比如证书错误或者路径错误,而不是需要调用mosquitto_tls_opts_set设置版本号。
mosquitto_tls_set调用失败不会导致connect错误,一开始socket会连接成功,后面会断开,因为默认会不加密,服务器判断报文类型错误报ssl3_get_record:wrong version number
,从而拒绝客户端接入。
1698072731: Socket error on client <unknown>, disconnecting.
1698072732: New connection from 220.23.13.85 on port 1883.
1698072732: New connection from 120.229.50.12 on port 1883.
1698072732: OpenSSL Error: error:1408F10B:SSL routines:ssl3_get_record:wrong version number
1698072732: Socket error on client <unknown>, disconnecting.
1698072733: New connection from 220.23.13.85 on port 1883.
1698072733: New connection from 120.229.50.12 on port 1883.
1698072733: OpenSSL Error: error:1408F10B:SSL routines:ssl3_get_record:wrong version number
1698072733: Socket error on client <unknown>, disconnecting.
1698072734: New connection from 220.23.13.85 on port 1883.
详细的MQTT C语言示例参考之前的MQTT教程。
Python实现MQTT SSL连接
python中ssl连接也很简单,加入以下代码即可
import ssl
client.tls_set(ca_certs="/etc/mosquitto/ca_certificates/ca.crt", certfile="/etc/mosquitto/ca_certificates/client.crt", keyfile="/etc/mosquitto/ca_certificates/client.key", cert_reqs=ssl.CERT_REQUIRED,
tls_version=ssl.PROTOCOL_TLS, ciphers=None)
client.tls_insecure_set(True)