ROS2安全通信使用DDS的安全机制,创造enclave安全飞地(直译,也可称为安全域),在安全飞地内,安全飞地内的进程受安全保护,进程相互之间进行安全通信。安全飞地内的进程与安全飞地外的进程通信受限(根据用户安全设置)。
具体使用了流程可以参考官网Security — ROS 2 Documentation: Humble documentation
以下说明以FastDDS为例简单介绍一些ROS2中的安全机制:
sros2工具
ROS2安全通信使用安全证书进行签名、身份认证、密钥交换等操作,开发调试环境可由本地用户担任server角色(也可采用专门的安全server),本地用户可同时担任server与client。
安全证书由ros2的sros2模块包创建和管理,生成的证书被放置在密钥库中。一个典型的密钥库包含以下结构:
其中,public与private为安全CA证书目录,enclaves为安全飞地证书目录,放置用户节点进程证书。
- public
主要包含ca.cert.pem,为CA公共证书, client验证指纹使用,所有客户端共用一份;identity_ca.cert.pem和 permissions_ca.cert.pem为指向 CA公共证书的链接,代替CA公共证书分别处理身份验证、访问控制等场景下的验证身份操作。
- private
主要包含ca.key.pem,为CA私钥,CA签名证书使用,由server自行保存;identity_ca.key.pem和 permissions_ca.key.pem为指向 CA私钥的链接,代替CA公共证书分别处理身份验证、许可等场景下签名操作。
- enclaves
cert.pem:此enclave实例的x.509证书(由IdentityCA 签名),包含公钥。
key.pem:此enclave实例的私钥。
identity_ca.cert.pem :指向身份验证插件信任的 CA 的 x.509 公共证书(“Identity”CA)的链接。
permissions_ca.cert.pem :指向访问控制插件信任的 CA 的 x.509 公共证书的链接。
governance.p7s :链接,指向访问控制插件指定保护域规则的 XML 文档(由CA 签名)
permissions.xml: 指定此特定 enclave 实例对访问控制插件的权限的 原始XML 文档.
permissions.p7s:以上permissions.xml经过CA签名后的副本,实际通信操作中使用此副本。
另外enclaves目录下有个公共的governance.p7s :为以上的安全飞地governance.p7s 链接指向的xml文档,由CA签名,governance.xml:为governance.p7s未经CA签名的原始xml文件。
证书身份验证与密钥交换
初始化CA根证书,可以client内置也可以终端首次运行时从服务端获取。
生成enclaves证书并上传服务器进行CA签名,同时本地保存好enclaves私钥,服务器使用根证书签名后返回证书。后续 DomainParticipant 就可以使用这个证书进行发现服务的身份鉴定。证书交换、签名过程如下:
Client使用CA根证书,通过验证证书的签名验证本地安全证书的合法性。
Client验证证书身份通过后,进入握手环节。
1)握手环节中p1(即participant1)验证远端p2(即participant2)证书后,生成本地随机数,经过一定的密钥算法计算为dh1后,将dh1与p1随机数用公钥加密后发给远端p2;
2)p2验证远端p1证书后,使用私钥解密获得dh1,生成本地随机数,计算生成dh2,将dh1、dh2、p2随机数用公钥加密后发给远端p1;p2使用本地随机数及dh1生成共享对称密钥。
3)p1使用本地随机数以及dh2计算生成共享对称密钥。
5)p1、p2经过计算获得协商后的对称加密密钥,完成握手及密钥交换。后续采用对称加密密钥进行加密通信。
ROS2使用插件实现身份验证、数据加密、访问控制等功能,其中实现身份验证的插件名为“DDS:Auth:PKI-DH”, 符合 DDS 安全规范。DDS:Auth:PKI-DH 插件使用受信任的证书颁发机构(CA) 和 ECDSA 数字签名算法来执行相互身份验证。它还使用椭圆曲线 Diffie-Hellman (ECDH) 密钥协商方法建立共享密钥,此共享密钥可以被其他安全插件如加密插件等使用。使用身份验证插件时,需要配置的主要属性包括身份_ca路径、身份证书路径、私钥路径。配置示例如下所示:
DomainParticipantQos pqos;
// Activate DDS:Auth:PKI-DH plugin
pqos.properties().properties().emplace_back("dds.sec.auth.plugin",
"builtin.PKI-DH");
// Configure DDS:Auth:PKI-DH plugin
pqos.properties().properties().emplace_back(
"dds.sec.auth.builtin.PKI-DH.identity_ca",
"file://maincacert.pem");
pqos.properties().properties().emplace_back(
"dds.sec.auth.builtin.PKI-DH.identity_certificate",
"file://partcert.pem");
pqos.properties().properties().emplace_back(
"dds.sec.auth.builtin.PKI-DH.identity_crl",
"file://crl.pem");
pqos.properties().properties().emplace_back(
"dds.sec.auth.builtin.PKI-DH.private_key",
"file://partkey.pem");
pqos.properties().properties().emplace_back(
"dds.sec.auth.builtin.PKI-DH.password",
"domainParticipantPassword");
身份验证的代码实现分析
-
初始化
在ros2/rmw_fastrtps/rmw_fastrtps_shared_cpp/participants.cpp,新建participants函数中,从rmw_security_options_t结构中获取密钥库路径、配置安全插件,包括配置启用“身份鉴定”插件、配置根证书、安全飞地证书、安全飞地私钥的路径等。
-
验证本地证书合法性
建立participants后,在eProsima/Fast-DDS/src/cpp/rtps/security/SecurityManager.cpp的SecurityManager::init()函数中调用
validate_local_identity()函数(函数实现在eProsima/Fast-DDS/security/authentication/PKIDH.cpp的PKIDH类中,该类继承自Authentication类),使用根证书,验证本地Participant的identity_certificate证书合法性,此函数返回当前 participant 的句柄IdentityHandle,以及在 DDS 网络中唯一的身份标识GUID_t。
-
其它participant初始化操作
在SecurityManager::init()函数中相继调用validate_local_permissions(),check_create_participant(),get_permissions_credential_token(),set_permissions_credential_and_token(),get_participant_sec_attributes()等函数,完成participant初始化。
-
握手
在PDPSimple.cpp的PDPSimple::assignRemoteEndpoints( )->SecurityManager.cpp的
SecurityManager::discovered_participant()函数中(即在发现远端participant时),调用
validate_remote_identity(),该函数实现在PKIDH.cpp中,验证远端证书合法性。
在PDPSimple.cpp的PDPSimple::assignRemoteEndpoints( )->SecurityManager.cpp的
SecurityManager::discovered_participant()->SecurityManager::on_process_handshake ()中根据paticipant握手状态分别调用begin_handshake_request()函数、begin_handshake_reply()函数与process_handshake()函数,这三个函数实现也都在PKIDH.cpp中。
on_process_handshake ()中,判断认证状态,若为未发送请求状态,调用begin_handshake_request()函数;若为未发送请求状态,调用begin_handshake_reply()函数;若为等待应答与等待结束状态,调用process_handshake()函数。
begin_handshake_request()函数对应“证书身份验证与密钥交换”节中握手的步骤1),本机发起握手请求,主要功能包括将dh1与p1随机数用公钥加密后发给远端(p1随机数本地保留),具体过程参见2.2.4节。
begin_handshake_reply()函数对应“证书身份验证与密钥交换”节中握手的步骤2),本机收到握手请求后回复握手请求,计算 dh2 发给 remote并使用本地随机数以及 dh1 生成共享对称密钥。
process_handshake()函数对应“证书身份验证与密钥交换”节中握手的步骤3),处理握手回复,使用本地随机数,以及 dh2 生成共享对称密钥。
握手及身份验证成功后,即可开始数据加密相关操作。在on_process_handshake ()中,身份认证成功后,调用get_shared_secret():获得协商后的对称加密密钥。