1. 前言
网络技术不断发展,网络中存在的攻击行为也层出不穷,恶意攻击者作为中间人窃取网络信息,篡改网络信息,或者冒充合法服务器的行为,也成为我们设计网络协议,搭建网络通信服务器时,必须重点考虑的问题。
MQTT(Message Queuing Telemetry Transport,消息队列遥测传输协议),是一种基于发布/订阅(publish/subscribe)模式的轻量级协议,该协议构建于TCP/IP协议之上,MQTT最大优点在于,可以以极少的代码和有限的带宽,为连接远程设备提供实时可靠的消息服务。
该协议默认情况下,以密文的形式进行消息的发布。因此,在本章节中,将通过TLS协议对MQTT发布的信息进行加密,确保消息的安全传输,保证数据的保密性和数据保证性。
最终效果是,虚拟机发生状态变化时,发布者会以事件的形式,通知到订阅者,发布的事件信息通过TLS协议进行加密。
2. MQTT协议的服务端和客户端如何获取证书?
2.1 服务端的配置
服务端,即broker,代理服务器。在broker上的配置非常重要。
在broker上生成好,CA证书,CA密钥,broker证书,broker密钥,client证书和client密钥。后续client,即发布者和订阅者上,只需要直接拷贝broker上生成好的CA证书,CA密钥,client证书和client密钥即可。
2.1.1生成CA证书和CA密钥
创建目录certs,在certs目录下创建目录ca,在ca目录下,使用如下命令:
openssl req -new -x509 -days 365 -extensions v3_ca -keyout ca.key -out ca.crt
这个地方的Common Name对于后续影响不大,设置为openssl,之类的都可以。
2.1.2生成broker密钥(即,broker.key)
在certs目录下创建目录broker,在broker目录下,使用如下命令:
openssl genrsa -out broker.key 2048
2.1.3 使用broker密钥,创建一个签名请求文件(即,broker.csr)
在broker目录下,使用如下命令:
openssl req -out broker.csr -key broker.key –new
注意:这里的Common Name非常重要!!必须填域名!!
broker上面填主机名就可以了,例如:61服务器的主机名为libing-dev-61,那么这里就填入libing-dev-61。
2.1.4将证书签名请求(csr)文件传递给我们的验证机构
在broker目录下,使用如下命令:
openssl x509 -req -in broker.csr -CA …/ca/ca.crt -CAkey …/ca/ca.key -CAcreateserial -out broker.crt -days 100
2.1.5生成client密钥(即,client.key)
在certs目录下创建目录client,在client目录下,使用如下命令:
openssl genrsa -out client.key 2048
2.1.6根据client密钥,创建client签名请求文件(即,client.csr)
在client目录下,使用如下命令:
openssl req -out client.csr -key client.key –new
这里的Common Name非常重要!!必须填域名!!
broker上面填主机名就可以了,例如:61服务器的主机名为libing-dev-61,那么这里就填入libing-dev-61。
2.1.7将证书签名请求(csr)文件传递给我们的验证机构
在client目录下,使用如下命令:
openssl x509 -req -in client.csr -CA …/ca/ca.crt -CAkey …/ca/ca.key -CAcreateserial -out client.crt -days 100
到了这里单机模式下的MQTT+TLS就完成了,可以先测试一下单机模式能不能通。
修改配置文件:
vim /etc/mosquitto/mosquitto.conf
重启mosquitt服务:
systemctl restart mosquitto.service
第一个窗口:
mosquitto_sub -p 18883 --cafile …/ca/ca.crt --cert client.crt --key client.key -h libing-dev-61 -t ab -d
第二个窗口:
mosquitto_pub -p 18883 --cafile …/ca/ca.crt --cert client.crt --key client.key -h libing-dev-61 -m hello -t ab –d
其中,-d表示调试,debug,会看到明显的发布和订阅信息
2.2 发布者上的配置
a. 使用scp将CA证书,CA密钥,client证书和client密钥拷贝到发布者服务器上。
b. 配置mosquitto.conf配置文件,
c. 重启mosquitto服务。
命令如下:
scp root@broker的IP地址:/root/cLan/mosquitto/certs/ca/ca.crt ./
scp root@broker的IP地址:/root/cLan/mosquitto/certs/ca/ca.key ./
scp root@broker的IP地址:/root/cLan/mosquitto/certs/client/client.crt ./
scp root@broker的IP地址:/root/cLan/mosquitto/certs/client/client.key ./
vim /etc/mosquitto/mosquitto.conf
systemctl restart mosquitto.service
d. 绑定ip地址和域名
注意:这一步非常重要!!因为MQTT+TLS访问的时候,是通过域名解析为IP地址,然后再根据IP地址去访问的!!
命令如下:
vim /etc/hosts
broker的IP地址 broker的主机名
在/etc/hosts文件中,加入一条ip地址到域名的映射,即broker的IP地址 broker的主机名。
2.3订阅者上的配置
订阅者的配置和发布者的配置是一样的。
a. 使用scp将CA证书,CA密钥,client证书和client密钥拷贝到发布者服务器上。
b. 配置mosquitto.conf配置文件,
c. 重启mosquitto服务。
命令如下:
scp root@broker的IP地址:/root/cLan/mosquitto/certs/ca/ca.crt ./
scp root@broker的IP地址:/root/cLan/mosquitto/certs/ca/ca.key ./
scp root@broker的IP地址:/root/cLan/mosquitto/certs/client/client.crt ./
scp root@broker的IP地址:/root/cLan/mosquitto/certs/client/client.key ./
vim /etc/mosquitto/mosquitto.conf
systemctl restart mosquitto.service
d. 绑定ip地址和域名
注意:这一步非常重要!!因为MQTT+TLS访问的时候,是通过域名解析为IP地址,然后再根据IP地址去访问的!!
命令如下:
vim /etc/hosts
broker的IP地址 broker的主机名
在/etc/hosts文件中,加入一条ip地址到域名的映射,即broker的IP地址 broker的主机名。
2.4 实验验证
通过命令的方式进行验证。
60作为发布者:
mosquitto_pub -p 18883 --cafile …/ca/ca.crt --cert client.crt --key client.key -h libing-dev-61 -m hello -t ab –d
50作为订阅者:
mosquitto_sub -p 18883 --cafile …/ca/ca.crt --cert client.crt --key client.key -h libing-dev-61 -t ab -d
发布者:
订阅者:
3. 代码实现
3.1 发布者
#include <stdio.h>
#include <stdlib.h>
#include <libvirt/libvirt.h>
#include <pthread.h>
#include <time.h>
#include <string.h>
#include "mosquitto.h"
#define KEEP_ALIVE 60
#define MSG_MAX_SIZE 512
static int myEventCallback(virConnectPtr conn,
virDomainPtr dom,
int event,
int detail,
void *opaque)
{
struct mosquitto *mosq = (struct mosquitto *)opaque;
const char *name = virDomainGetName(dom);
struct tm *currTime;
time_t now;
time(&now);
currTime = localtime(&now);
char buff[MSG_MAX_SIZE];
snprintf(buff, MSG_MAX_SIZE, "%d/%d/%d %d:%d:%d event(%d) occurred in the domain = < %s >.\n", currTime->tm_year + 1900, currTime->tm_mon + 1, currTime->tm_mday, currTime->tm_hour, currTime->tm_min, currTime->tm_sec, event, name);
mosquitto_publish(mosq, NULL, "topic1", strlen(buff) + 1, buff, 0, 0);
return 0;
}
static void* eventThreadLoop() {
while(1) {
if(virEventRunDefaultImpl() < 0) {
printf("Run errer.\n");
}
}
abort();
}
int main(int argc, char *argv[]) {
if(argc != 3) {
fprintf(stderr, "please input:./keepPub ip port.\n");
return -1;
}
const char *host = argv[1];
int port = atoi(argv[2]);
virConnectPtr conn = NULL;
int eventid = VIR_DOMAIN_EVENT_ID_LIFECYCLE;
virEventRegisterDefaultImpl();
conn = virConnectOpen(NULL);
if(conn == NULL) {
fprintf(stderr, "connect hypervisor is failed.\n");
return -1;
}
pthread_t eventThread;
pthread_create(&eventThread, NULL, eventThreadLoop, NULL);
int ret;
struct mosquitto *mosq;
ret = mosquitto_lib_init();
if(ret) {
fprintf(stderr, "mosquitto lib init is failed.\n");
return -1;
}
mosq = mosquitto_new("Pub", true, NULL);
if(mosq == NULL) {
fprintf(stderr, "create mosquitto instance is failed.\n");
return -1;
}
const char *cafile = "/root/cLan/certs/ca/ca.crt";
const char *capath = "/root/cLan/certs/ca";
const char *certfile = "/root/cLan/certs/client/client.crt";
const char *keyfile = "/root/cLan/certs/client/client.key";
ret = mosquitto_tls_set(mosq, cafile, capath, certfile, keyfile, NULL);
if(ret) {
fprintf(stderr, "TLS certification is failed.\n");
return -1;
}
ret = mosquitto_connect(mosq, host, port, KEEP_ALIVE);
if(ret) {
fprintf(stderr, "connect broker is failed.\n");
return -1;
}
ret = mosquitto_loop_start(mosq);
if(ret) {
fprintf(stderr, "mosquitto loop is failed.\n");
return -1;
}
int id = virConnectDomainEventRegisterAny(conn,
NULL,
eventid,
VIR_DOMAIN_EVENT_CALLBACK(myEventCallback),
mosq,
NULL);
while(1) pause();
mosquitto_disconnect(mosq);
mosquitto_destroy(mosq);
mosquitto_lib_cleanup();
virConnectDomainEventDeregisterAny(conn, id);
virConnectClose(conn);
return 0;
}
3.2 订阅者
#include <stdio.h>
#include <stdlib.h>
#include "mosquitto.h"
#define HOST "broker的主机名"
#define PORT 18883
#define KEEPALIVE 60
int running = 1;
void my_connect_callback(struct mosquitto *mosq, void *obj, int rec) {
printf("Call the function: my_connect_callback.\n");
if(rec){
printf("On_connect error.\n");
exit(1);
} else {
if(mosquitto_subscribe(mosq, NULL, "topic1", 0)) {
printf("Set topic error.\n");
exit(1);
}
}
}
void my_disconnect_callback(struct mosquitto *mosq, void *obj, int rec) {
printf("Call the function: my_disconnect_callback.\n");
running = 0;
}
void my_subscribe_callback(struct mosquitto *mosq, void *obj, int mid, int qos_count, const int*granted_qos) {
printf("Call the function: my_subscribe_callback.\n");
}
void my_message_callback(struct mosquitto *mosq, void *obj, const struct mosquitto_message *msg) {
printf("Call the function: my_message_callback.\n");
printf("Receive a message %s: %s", (char*)msg->topic, (char*)msg->payload);
if(0 == strcmp((char*)msg->payload, "quit")) {
mosquitto_disconnect(mosq);
}
}
int main() {
int ret = 0;
struct mosquitto *mosq;
ret = mosquitto_lib_init();
if(ret) {
printf("init lib is failed.\n");
return -1;
}
mosq = mosquitto_new("sub", true, NULL);
if(mosq == NULL) {
printf("create new mosquitto instance failed.\n");
mosquitto_lib_cleanup();
return -1;
}
mosquitto_connect_callback_set(mosq, my_connect_callback);
mosquitto_disconnect_callback_set(mosq, my_disconnect_callback);
mosquitto_subscribe_callback_set(mosq, my_subscribe_callback);
mosquitto_message_callback_set(mosq, my_message_callback);
const char *cafile = "/root/cLan/mosquitto/certs/ca/ca.crt";
const char *capath = "/root/cLan/mosquitto/certs/ca";
const char *certfile = "/root/cLan/mosquitto/certs/client/client.crt";
const char *keyfile = "/root/cLan/mosquitto/certs/client/client.key";
ret = mosquitto_tls_set(mosq, cafile, capath, certfile, keyfile, NULL);
if(ret) {
printf("TLS certification is failed.\n");
mosquitto_destroy(mosq);
mosquitto_lib_cleanup();
return -1;
}
ret = mosquitto_connect(mosq, HOST, PORT, KEEPALIVE);
if(ret) {
printf("connect to broker is failed.\n");
mosquitto_destroy(mosq);
mosquitto_lib_cleanup();
return -1;
}
printf("Subscribe begin.\n");
int loop = mosquitto_loop_start(mosq);
if(loop) {
printf("mosquitto loop error.\n");
return -1;
}
const char cmd[10];
while(running) {
scanf("%s", cmd);
if(0 == strcmp(cmd, "quit")) {
running = 0;
}
}
mosquitto_disconnect(mosq);
mosquitto_destroy(mosq);
mosquitto_lib_cleanup();
printf("Subscribe end.\n");
return 0;
}
3.3 实验效果
在broker上使用tcpdump进行抓包,可以获取发布者发送的数据,通过wireshark打开,可以看到信息都通过TLS协议进行加密。
参考文献
[1] https://openest.io/en/services/mqtts-how-to-use-mqtt-with-tls/
[2] https://blog.csdn.net/Jacobsea/article/details/125616913
[3] https://blog.csdn.net/Jacobsea/article/details/125613457