在工业互联网等环境中,终端设备计算资源有限、终端设备拓扑变化复杂,为了满足传输安全的需要,在TLSv1.3协议的基础之上,设计了轻量级的CL-TLS协议。CL-TLS协议简化了报文结构,引入了轻量级的ASCON-128A加密算法和ASCON-HashA哈希算法,同时使用无证书公钥密码体制(CLPKC)取代PKI机制进行身份认证。
使用CL-TLS协议的系统运行时,需要使用到如下应用层协议:
- KGC协议
在本实现中,CL-TLS以MQTT代理服务器的形式运行,使用了下列应用层协议:
- MQTT协议
- CONNCTL协议
KGC协议和CONNCTL协议的详细作用将在后面说明。
Client Server
Key ^
Exch | ClientHello
v -------->
^ Key
ServerHello | Exch
v
^ Server
{PublicKeyRequest*} | Params
v
{PublicKey} ^
{PublicKeyVerify} | Auth
<-------- {Finished} v
^ {PublicKey*}
Auth | {PublicKeyVerify*}
v {Finished} -------->
[Application Data] <-------> [Application Data]
* Indicates optional or situation-dependent
messages/extensions that are not always sent.
{} Indicates messages protected using keys
derived from a [sender]_handshake_traffic_secret.
[] Indicates messages protected using keys
derived from [sender]_application_traffic_secret_N.
CL-TLS使用基于CLPKC的方案进行身份认证。每个设备均拥有自己的身份ID和密钥对,其中设备密钥对由设备自身和KGC共同生成,使用KGC的私钥和设备ID即可验证设备公钥是否属于该设备。
-
新服务端设备注册流程
- 设备选取
$Seed_A\gets\{0,1\}^{256}$ ; - 设备生成部分密钥
$(PK_A,SK_A):=Ed25519GenKeypair(Seed_A)$ ; - 设备将自身身份
$ID$ 和$PK_A$ 发送给KGC; - KGC选取
$Seed_B\gets\{0,1\}^{256}$ ; - KGC生成部分密钥
$(PK_B,SK_B):=Ed25519GenKeypair(Seed_B)$ ; - KGC计算
$S:=Sign_{SK_{KGC}}(ID||PK_A||PK_B)$ ; - KGC将
$(PK_B,SK_B,S)$ 发回设备; - 设备保存公钥
$PK:=PK_A||PK_B||S$ ,私钥$SK:=SK_A||SK_B$
- 设备选取
-
新客户端设备注册流程
- 设备选取
$Seed_A\gets\{0,1\}^{256}$ ; - 设备生成部分密钥
$(PK_A,SK_A):=Ed25519GenKeypair(Seed_A)$ ; - 设备将自身身份
$ID$ 和$PK_A$ 发送给KGC; - 设备将自身所属的所有服务端的身份集合
$ID_S$ 发送给KGC; - KGC选取
$Seed_B\gets\{0,1\}^{256}$ ; - KGC生成部分密钥
$(PK_B,SK_B):=Ed25519GenKeypair(Seed_B)$ ; - KGC计算
$S:=Sign_{SK_{KGC}}(ID||PK_A||PK_B)$ ; - KGC通知
$ID_S$ 中的每一个服务端,添加$ID$ 到其允许来访的身份列表中; - KGC将
$(PK_B,SK_B,S)$ 发回设备; - 设备保存公钥
$PK:=PK_A||PK_B||S$ ,私钥$SK:=SK_A||SK_B$
- 设备选取
特别地,当整个系统从零开始部署时,KGC的密钥对通过以下流程生成:
- KGC密钥对初始化生成流程
- KGC选取
$Seed_A\gets\{0,1\}^{256}$ ; - KGC生成部分密钥
$(PK_A,SK_A):=Ed25519GenKeypair(Seed_A)$ ; - KGC选取
$Seed_B\gets\{0,1\}^{256}$ ; - KGC生成部分密钥
$(PK_B,SK_B):=Ed25519GenKeypair(Seed_B)$ ; - KGC计算
$S:=Sign_{SK_A||SK_B}(ID||PK_A||PK_B)$ - KGC保存公钥
$PK:=PK_A||PK_B||S$ ,私钥$SK:=SK_A||SK_B$
- KGC选取
-
$Sign_{SK_A||SK_B}(m)$ :$S_1:=Ed25519Sign_{SK_A}(m),S_2:=Ed25519Sign_{SK_B}(m),输出S:=S_1||S_2$ -
$Vrfy_{PK_A||PK_B}(m,S_1||S_2)$ :$输出Ed25519Vrfy_{PK_A}(m,S_1)\wedge Ed25519Vrfy_{SK_B}(m,S_2)$
除了KGC以外,每个服务端都维护一个允许对自己进行访问的设备ID列表。属于某个服务器的客户端在进行注册时,KGC将会通知该服务器添加新客户端的设备ID到自己的允许访问列表中。
ClientHello消息中包含客户端的ID,服务端可以在收到后立即进行检查。
客户端和服务端在PublicKey消息中发送自己的公钥
客户端和服务端在PublicKeyVerify消息中发送使用自己的私钥
除了新设备向KGC注册时不需要发送PublicKey和PublicKeyVerify以外,其余所有情况下连接握手时都需要验证双方身份。
在整个通信过程中,如果任意一方出现错误,则向对方发送一个Error Stop Notify报文,其中包含错误代码,然后关闭TCP连接。对方收到后,可对错误进行展示和记录,然后终止会话。
在本实现中,CL-TLS以代理服务器的方式工作,传输MQTT应用层协议。
cltls_client:CL-TLS客户端代理服务器cltls_server:CL-TLS服务端程序,可以代理服务器模式运行或以KGC模式运行cltls_misc_mqtt_client:演示用的简单MQTT客户端程序cltls_misc_mqtt_server:演示用的简单MQTT服务端程序cltls_misc_initializer:KGC密钥对生成程序
--------------------
| KGC Device |
| |
| -------------- |
-------+->| |<-+-------
| | | KGC Server | | |
| ---+--| |--+--- |
| | | -------------- | | |
| | | IDk PKk SKk | | |
| | -------------------- | |
| | | |
------------------------------+---+--------- ----------+---+-----------------------------
| Client Device | | | | | | Server Device |
| | v | | v | |
| --------------- ----------------- | | ----------------- --------------- |
| | |----->| |--+-----+->| |----->| | |
| | MQTT Client | | CL-TLS Client | | | | CL-TLS Server | | MQTT Server | |
| | |<-----| |<-+-----+--| |<-----| | |
| --------------- ----------------- | | ----------------- --------------- |
| IDc PKc SKc | | IDs PKs SKs PermittedIDs |
-------------------------------------------- --------------------------------------------
下面说明一个最简系统的工作流程,该系统由一个KGC设备、一个客户端设备和一个服务端设备组成。
- KGC设备
- IP地址:
192.168.7.60 - ID:
ECECECECECECECEC - 目录结构(除可执行文件外,均为空文件):
- IP地址:
cltls
|---kgc
| |---cltls_server
| |---config.conf
| |---permitted_ids.txt
|
|---common
| |---idip.txt
|
|---cltls_misc_initializer
- 客户端设备
- IP地址:
192.168.7.120 - ID:
AA00000000000001 - 目录结构(除可执行文件外,均为空文件):
- IP地址:
cltls
|---client
| |---cltls_client
| |---config.conf
| |---bs.txt
|
|---common
| |---idip.txt
| |---kgc_pubkey.key
|
|---cltls_misc_mqtt_client
- 服务端设备
- IP地址:
192.168.7.180 - ID:
BB00000000000001 - 目录结构(除可执行文件外,均为空文件):
- IP地址:
cltls
|---server
| |---cltls_server
| |---config.conf
| |---permitted_ids.txt
|
|---common
| |---idip.txt
| |---kgc_pubkey.key
|
|---cltls_misc_mqtt_server
在从零部署CL-TLS应用环境时,首先使用的cltls_misc_initializer程序为KGC生成密钥对。在KGC设备的cltls目录内,执行./cltls_misc_initializer kgc,即可生成KGC密钥对文件pubkey.key和privkey.key并把它们存储在kgc子目录中。同时,将pubkey.key分发到所有其他设备的cltls/common目录中,命名为kgc_pubkey.key。
类似DNS系统,CL-TLS代理服务器使用一个本地维护的数据库文件来存储从设备ID到设备IP地址的映射关系。在每个设备的cltls/common目录内,都新建一个idip.txt,内容为:
ECECECECECECECEC 192.168.7.60
AA00000000000001 192.168.7.120
BB00000000000001 192.168.7.180
虽然在本示例中客户端设备不会被其他设备主动连接,但仍然将其ID/IP映射加入到数据库中,为以后在设备上同时运行服务端的可能做好准备。
编辑KGC服务器的配置文件cltls/kgc/config.conf:
IDENTITY=ECECECECECECECEC
PUBLIC_KEY=pubkey.key
PRIVATE_KEY=privkey.key
KGC_PUBLIC_KEY=pubkey.key
IDIP_DATABASE=../common/idip.txt
PERMITTED_IDS_DATABASE=permitted_ids.txt
SOCKET_BLOCK_SIZE=2097152
注意KGC在握手阶段不检查客户端ID,允许所有客户端建立连接,其permitted_ids.txt内容为空。
保存配置后,进入cltls/kgc目录,执行./cltls_server -m KGC即可运行KGC。
编辑服务端的配置文件cltls/server/config.conf:
IDENTITY=BB00000000000001
PUBLIC_KEY=pubkey.key
PRIVATE_KEY=privkey.key
KGC_PUBLIC_KEY=../common/kgc_pubkey.key
IDIP_DATABASE=../common/idip.txt
PERMITTED_IDS_DATABASE=permitted_ids.txt
SOCKET_BLOCK_SIZE=2097152
保存配置后,进入cltls/server目录,执行./cltls_server -r即可完成注册,此时服务端得到的密钥对已经被存储在了配置文件里指定的文件中。
首先启动服务器设备上的MQTT服务端程序。回到cltls目录,执行./cltls_misc_mqtt_server 22601。
然后进入cltls/server,启动CL-TLS服务端:./cltls_server -m PROXY -p 22600 --fwd-ip 127.0.0.1 --fwd-port 22601
CL-TLS服务端还支持的可选选项是:
-l, --log=<str>:日志打印级别,可为ERROR|WARN|INFO之一,默认为INFO;--cipher=<str>:优先使用的密码学算法套件,可为ASCON128A_ASCONHASHA|ASCON128A_SHA256|AES128GCM_ASCONHASHA|AES128GCM_SHA256之一,默认为ASCON128A_ASCONHASHA。服务端设备可以通过此选项选择在自身平台上性能或资源占用最佳的算法套件;-c, --config=<str>:配置文件路径,默认为config.conf;-t, --timing:是否打印握手和MQTT代理转发耗时,默认不打印。
编辑客户端的配置文件cltls/client/config.conf:
IDENTITY=AA00000000000001
PUBLIC_KEY=pubkey.key
PRIVATE_KEY=privkey.key
KGC_PUBLIC_KEY=../common/kgc_pubkey.key
IDIP_DATABASE=../common/idip.txt
SOCKET_BLOCK_SIZE=2097152
保存配置后,再编辑客户端所属的服务器ID及其代理服务器端口号列表文件cltls/client/bs.txt:
BB00000000000001 22600
此文件中服务端的代理服务器端口号仅用于注册阶段KGC向服务器发起连接。
进入cltls/client目录,执行./cltls_client -r --bs bs.txt即可完成注册,此时客户端得到的密钥对已经被存储在了配置文件里指定的文件中,所属的服务端也将本客户端的ID加入到了允许来访的ID列表中。
然后即可启动CL-TLS客户端:./cltls_client -p 23600
回到cltls目录,启动MQTT客户端程序:./cltls_misc_mqtt_client 127.0.0.1 23600
CL-TLS客户端还支持的可选选项是:
-l, --log=<str>:日志打印级别,可为ERROR|WARN|INFO之一,默认为INFO;-c, --config=<str>:配置文件路径,默认为config.conf;-t, --timing:是否打印握手和MQTT代理转发耗时,默认不打印。
在MQTT客户端内,输入CONN BB00000000000001 22600并回车。MQTT客户端会给CL-TLS客户端发送一个CONNCTL协议的Connect Request消息,CL-TLS客户端会在ID/IP表中查找到服务器IP后与CL-TLS服务端建立TCP连接并进行握手,完成后CL-TLS服务端会与本地MQTT服务端建立连接。上述流程均成功后,CL-TLS服务端会发回CONNCTL协议的Connect Response消息,状态代码为成功。CL-TLS客户端收到后,会向MQTT客户端发送一个CONNCTL协议的Connect Response消息,状态代码为成功。然后即可开始在MQTT客户端内传输数据了。
输入PUBLISH 32并回车,客户端将发出一个载荷大小为32字节的MQTT PUBLISH消息并打印载荷内容。收到服务端的MQTT PUBLISH响应后,消息载荷也会被打印出来。可以对比服务端所打印的载荷内容,验证消息被正确传输。
然后再输入PUBLISH 268435455并回车,将发送一个载荷大小为256MB-1的MQTT PUBLISH消息,这也是单个MQTT消息能承载的最大载荷大小,将会打印消息首尾16字节的数据。CL-TLS客户端和服务端配置文件中的SOCKET_BLOCK_SIZE选项指定了套接字收发时的最大块长度,超过此长度的消息将被分成多块收发,以控制收发双方用于套接字收发的缓冲区大小。
最后,输入DISCONNECT并回车,整个会话将结束。
CLTLS-MQTT-GUI是配套CL-TLS代理服务器的MQTT GUI演示程序,可以可视化地进行MQTT数据的安全传输。使用方法请见该仓库内的README。