1.背景
无论是ROS机器人系统还是在车载系统领域,DDS的主要应用场景都是在近场传输方面,近场传输的数据安全性需要一定的保证,SOME/IP,CAN总线,ROSTCP,ROSUDP都无法提供内置的安全插件,为了满足安全方面的需求需要额外的安全软件的开发。早期的车载系统中安全防护方面是非常薄弱的,基本属于裸奔状态,随着这些年车载系统的发展,座舱系统对于安全性方面的要求越来越高。早期的这些消息中间件无法适应这些安全性方面的要求。
DDS提供了一系列的安全机制,用于保护数据的传输和存储。它支持数据加密、身份验证、访问控制等安全特性,可以有效地防止数据泄露和未经授权的访问。FASTDDS提供了符合DDS-Security标准的安全插件,我们在使用的时候,只需要做相关的配置就可以了。
2.FASTDDS的安全插件
FASTDDS提供了一些安全插件,这些插件是符合DDS-SECURITY标准的。
一共有五种插件:
授权插件(Auth plug)。主要是身份认证。
访问权插件(Access plug)。在认证完成后,对什么样的数据进行什么样的加密,由这个插件控制。
加密算法插件(Cryptographic plug)。采用什么样的算法对数据较密,由这个插件控制。
日志插件(Logging plug)。对日志采用什么样的方式加密,由这个插件控制。
数据tag插件(Data tagging plug)。对数据进行区分,给dds消息打上tag,那么对于某些有tag的消息,可以采用加密手段,这样对数据区分得更细。
这里主要介绍 授权插件 和 访问权插件。其他插件用的相对较少,也不是主要流程,这里不做介绍了。这里以一个安全代码的示例做解析。
2.1安全代码示例
发送端Publisher的初始化
scss
bool HelloWorldPublisher::init()
{
hello_.index(0);
hello_.message("HelloWorld");
//CREATE THE PARTICIPANT
DomainParticipantQos pqos;
//使用内置的PKI-DH 插件
pqos.properties().properties().emplace_back("dds.sec.auth.plugin",
"builtin.PKI-DH");
//加载PKI-DH相关的证书
pqos.properties().properties().emplace_back("dds.sec.auth.builtin.PKI-DH.identity_ca",
"file://certs/maincacert.pem");
pqos.properties().properties().emplace_back("dds.sec.auth.builtin.PKI-DH.identity_certificate",
"file://certs/mainpubcert.pem");
pqos.properties().properties().emplace_back("dds.sec.auth.builtin.PKI-DH.private_key",
"file://certs/mainpubkey.pem");
//使用内置的Access-Permissions插件
pqos.properties().properties().emplace_back("dds.sec.access.plugin",
"builtin.Access-Permissions");
//加载Access-Permissions相关的证书
pqos.properties().properties().emplace_back("dds.sec.access.builtin.Access-Permissions.permissions_ca",
"file://certs/maincacert.pem");
pqos.properties().properties().emplace_back("dds.sec.access.builtin.Access-Permissions.governance",
"file://certs/governance.smime");
pqos.properties().properties().emplace_back("dds.sec.access.builtin.Access-Permissions.permissions",
"file://certs/permissions.smime");
//使用内置的AES-GCM-GMAC插件,这个是加密算法插件
pqos.properties().properties().emplace_back("dds.sec.crypto.plugin",
"builtin.AES-GCM-GMAC");
------
return true;
}
这是HelloWorldPublisher初始化函数,其他部分和第一章的内容基本一样,这里不再解析。
这些代码主要意思是做Security相关的配置。
1.使用内置的PKI-DH 插件,加载PKI-DH相关证书。这是授权插件(Auth plug)。
加载的是3个东西就是我们2.1节介绍的DomainParticipantQos中被配置的3个文件。
2.使用内置的Access-Permissions插件,加载Access-Permissions相关的证书。这是访问权插件(Access plug)。
3.使用内置的AES-GCM-GMAC插件(这个是加密算法插件)。这是加密算法插件(Cryptographic plug)。
接收端Subscriber的初始化
scss
bool HelloWorldSubscriber::init()
{
//CREATE THE PARTICIPANT
DomainParticipantQos pqos;
//使用内置的PKI-DH 插件
pqos.properties().properties().emplace_back("dds.sec.auth.plugin",
"builtin.PKI-DH");
//加载PKI-DH相关的证书
pqos.properties().properties().emplace_back("dds.sec.auth.builtin.PKI-DH.identity_ca",
"file://certs/maincacert.pem");
pqos.properties().properties().emplace_back("dds.sec.auth.builtin.PKI-DH.identity_certificate",
"file://certs/mainsubcert.pem");
pqos.properties().properties().emplace_back("dds.sec.auth.builtin.PKI-DH.private_key",
"file://certs/mainsubkey.pem");
//使用内置的Access-Permissions插件
pqos.properties().properties().emplace_back("dds.sec.access.plugin",
"builtin.Access-Permissions");
//加载Access-Permissions相关的证书
pqos.properties().properties().emplace_back(
"dds.sec.access.builtin.Access-Permissions.permissions_ca",
"file://certs/maincacert.pem");
pqos.properties().properties().emplace_back(
"dds.sec.access.builtin.Access-Permissions.governance",
"file://certs/governance.smime");
pqos.properties().properties().emplace_back(
"dds.sec.access.builtin.Access-Permissions.permissions",
"file://certs/permissions.smime");
//使用内置的AES-GCM-GMAC插件,这个是加密算法插件
pqos.properties().properties().emplace_back("dds.sec.crypto.plugin",
"builtin.AES-GCM-GMAC");
------
}
这是HelloWorldSubscriber初始化函数,其他部分和第一章的内容基本一样,这里不再解析。
这些代码主要意思是做Security相关的配置。
1.使用内置的PKI-DH 插件,加载PKI-DH相关证书。这是授权插件(Auth plug)。
2.使用内置的Access-Permissions插件,加载Access-Permissions相关的证书。这是访问权插件(Access plug)。
3.使用内置的AES-GCM-GMAC插件。这是加密算法插件(Cryptographic plug)。
在发送和接收两端都需要进行Security的相关配置,动作基本是相同的,加载的证书内容却并不完全相同。
2.2PKI-DH插件
这是身份认证的插件。PKI是Public Key Infrastructure(公共密钥基础设施)。DH是(Diffie-Hellman密钥交换算法)
这个内置的身份验证插件被称为"DDS:Auth:PKI-DH"。DDS:Auth:PKI-DH插件使用受信任的证书(CA证书)实现身份验证。这个插件在Participant之间使用标准的数字签名算法来实现身份的互相验证。它使用数字签名算法来建立身份信任链并签名
身份验证消息。它使用标准密钥建立算法在Participant之间创建了一个共享密钥,以创建一个对等安全通道。CA可能是一个现有的CA。
或者可以创建一个新的用于部署DDS域上的应用程序。选择CA的性质或方式并不重要。因为它的使用方式强制所有参与的应用程序进行
共享识别。想要使用这个插件,DomainParticipant必须配置三个文件:
- CA证书,里面包含公钥。
- DomainParticipant的私钥。
- 由前面CA证书,签名的CA身份证书,里面包含DomainParticipant相关的公钥。
内置的身份认证插件需要在DomainParticipantQos中被配置,主要的配置参数有这几个:
1.identity_ca 就是CA证书,身份证书。
2.identity_certificate 由identity_ca签发的证书,这个证书包含对应的DomainParticipant的公钥。
3.private_key 就是DomainParticipant的私钥。
这个插件主要完成的功能是身份认证,同时使用DH算法在两端协商一个加密密钥,这个密钥用来数据加密。
1.ParticipantA向发ParticipantB送HandshakeRequestMessageToken消息 ,这个消息里面包含了一个ParticipantA的证书,C1证书,一个随机生成的chanllege1,一个通过自身私钥生成DH1。
2.ParticipantB收到ParticipantA发送的HandshakeRequestMessageToken消息,解析消息,拿到C1证书,验明身份,向ParticipantA发送HandshakeReplyMessageToken消息,这个消息里面包含了一个ParticipantB的C2证书,包含一个随机生成的chanllege2,一个通过自身私钥生成的DH2,DH1,chanllege1,还有一个依据这些变量的签名sign2。
3.ParticipantA收到ParticipantB发送的HandshakeReplyMessageToken消息,解析消息,拿到c2证书,验证身份,通过sign2和公钥验证数据可靠性,拿到DH2,通过自身私钥和DH2生成secret,这个secret用于对称的数据加密,之后向ParticipantB发送HandshakeFinalMessageToken消息,这个消息包含chanllege1,DH2,DH1,chanllege2,还有一个依据这些变量的签名sign1。这个消息是对HandshakeReplyMessageToken的回应,内容也是类似。
4.ParticipantB收到ParticipantA发送的HandshakeFinalMessageToken消息,验证签名sign1,通过通过自身私钥和DH1生成secret。这个secret和在ParticipantA生成的secret是一样的。
通过这个些消息的传递,ParticipantB和ParticipantA协商了一个公共的secret,这个secret用户对称加密的AES算法。之后的消息加密解密就是使用这个secert。
PKI-DH 插件完成身份认证之后,产生共同的密钥(sharedsecret)。
整个过程就是DH算法的在DDS上的一个实现。
2.3Access:Permissions 插件
这个内置的访问控制插件被称为"DDS:Access:Permissions"插件。DDS:Access:Permissions通过一个由共享CA签署的文档实现访问控制。共享CA可以是现有的CA(包括用于身份验证的相同CA插件),或者可以创建一个新的插件在DDS域来为应用程序分配权限。 每个DomainParticipant都有一个DDS:Access:Permissions插件的关联实例。
这个DDS:Access:Permissions插件是基于PKI-DH插件实现的。需要配置3个文件:
- Permissions的CA 证书
- 被 Permissions CA 签名的governance文件
- 被 Permissions CA 签名的permissions文件
这些需要在需要在DomainParticipantQos中被配置,主要的配置参数有这几个:
1.permissions_ca 就是Permissions的CA 证书。
2.governance文件,就是签名的管理(governance)文件。
3.permissions文件,就是签名的权限(permissions)文件。
Permissions的CA 证书可以是和PKI-DH插件中的CA证书(identity_ca)一样的证书。
2.3.1governance文件
基本的保护类型有3种:
NONE,ENCRYPT,SIGN
NONE就是没有保护措施,SIGN就是比较比较低的保护措施,加一个校验码
ENCRYPT就是加密,这儿一般是AES加密,解密。
保护类型是2种:
SIGN_WITH_ORIGIN_AUTHENTICATION 就是身份认证之后,使用签名加一个校验码,数据不会被加密。
和ENCRYPT_WITH_ORIGIN_AUTHENTICATION就是身份认证之后,给数据加密。
所以总共有5种。
governance文件示例:
xml
<?xml version="1.0" encoding="utf-8"?>
<dds xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="omg_shared_ca_domain_governance.xsd">
<domain_access_rules>
<domain_rule>
<domains>
<id_range>
<min>0</min>
<max>230</max>
</id_range>
</domains>
<allow_unauthenticated_participants>false</allow_unauthenticated_participants>
<enable_join_access_control>true</enable_join_access_control>
<discovery_protection_kind>ENCRYPT</discovery_protection_kind>
<liveliness_protection_kind>ENCRYPT</liveliness_protection_kind>
<rtps_protection_kind>ENCRYPT</rtps_protection_kind>
<topic_access_rules>
<topic_rule>
<topic_expression>HelloWorldTopic</topic_expression>
<enable_discovery_protection>true</enable_discovery_protection>
<enable_liveliness_protection>false</enable_liveliness_protection>
<enable_read_access_control>true</enable_read_access_control>
<enable_write_access_control>true</enable_write_access_control>
<metadata_protection_kind>ENCRYPT</metadata_protection_kind>
<data_protection_kind>ENCRYPT</data_protection_kind>
</topic_rule>
</topic_access_rules>
</domain_rule>
</domain_access_rules>
</dds>
governance文件中一般包含这几个设置项:
-
domains 设置项,表示需要在哪个域(Domain)内应用这些加密设置。
在上图文件件中设置为domain id 是0-230的domain。
-
allow_unauthenticated_participants设置项,是否允许未经过授权的 participant加入进来。
在上图文件中设置为false。
-
enable_join_access_control设置项,is_access_protected这个变量的值跟随这个值。
-
discovery_protection_kind设置项,可以有5个选项
NONE,SIGN,ENCRYPT,SIGN_WITH_ORIGIN_AUTHENTICATION,ENCRYPT_WITH_ORIGIN_AUTHENTICATION
这是是EDP阶段是否加保护,以及采用什么级别的保护措施
-
liveliness_protection_kind设置项,
可以有5个选项NONE,SIGN,ENCRYPT,SIGN_WITH_ORIGIN_AUTHENTICATION,ENCRYPT_WITH_ORIGIN_AUTHENTICATION
对liveliness消息采用什么级别的保护措施
-
rtps_protection_kind设置项,
可以有5个选项NONE,SIGN,ENCRYPT,SIGN_WITH_ORIGIN_AUTHENTICATION,ENCRYPT_WITH_ORIGIN_AUTHENTICATION
-
rtps_psk_protection_kind设置项,
对PDP消息采取的保护措施,3个选项NONE, SIGN, or ENCRYPT,ENCRYPT的密钥和其他的密钥不一样是提前设置的密钥。
-
allowed_crypto_algorithms设置项,
对于两端签名,加密算法等算法的设置。
-
topic_rule设置项,
里面有很多小的设置项。
Publisher的governance文件需要和Subscriber的governance文件保持一致。
2.3.2permissions文件
xml
<permissions>
<grant name="PublisherPermissions">
<subject_name>[email protected], CN=Main Publisher, OU=eProsima, O=eProsima, ST=MA, C=ES</subject_name>
<validity>
<not_before>2013-06-01T13:00:00</not_before>
<not_after>2038-06-01T13:00:00</not_after>
</validity>
<allow_rule>
<domains>
<id_range>
<min>0</min>
<max>230</max>
</id_range>
</domains>
<publish>
<topics>
<topic>HelloWorldTopic</topic>
</topics>
</publish>
</allow_rule>
<default>DENY</default>
</grant>
<grant name="SubscriberPermissions">
<subject_name> [email protected], CN=Main Subscriber, OU=eProsima, O=eProsima, ST=MA, C=ES</subject_name>
<validity>
<not_before>2013-06-01T13:00:00</not_before>
<not_after>2038-06-01T13:00:00</not_after>
</validity>
<allow_rule>
<domains>
<id_range>
<min>0</min>
<max>230</max>
</id_range>
</domains>
<subscribe>
<topics>
<topic>HelloWorldTopic</topic>
</topics>
</subscribe>
</allow_rule>
<default>DENY</default>
</grant>
</permissions>
这个部分分为基本对称的两个部分。
grant 的名字是SubscriberPermissions 和PublisherPermissions
grant内部又分为3个部分
1.subject_name部分
这个名字和CA证书是一一对应的
PublisherPermission的subject_name 是和mainpubcert.pem里面的名字是一致的。
PublisherPermissions的subject_name 是和mainsubcert.pem里面的名字是一致的。
2.validity部分
表示这个规则生效的时间是什么时间段
3.规则部分(allow, deny 和 default 选项)
这个文件中就是allow 和 default。
allow_rule 这里设置了domain和topic名字,就是说在这个domain范围中的topic使用这些规则。
deault这个设置的是DENY,就是默认的情况下不使用这些规则。
3.源码解析
3.1创建安全相关的PDP端点
由于增加了安全插件,PDP的节点数有了一定的增加。
c_EntityId_spdp_reliable_participant_secure_reader和c_EntityId_spdp_reliable_participant_secure_writer
kotlin
RTPSParticipantImpl的构造函数中
if (!m_security_manager.init(security_attributes_, m_att.properties))
{
// Participant will be deleted, no need to allocate buffers or create builtin endpoints
return;
}
完成初始化的工作。
在PDPSimple
arduino
bool PDPSimple::createPDPEndpoints()
{
------
#if HAVE_SECURITY
fastdds::rtps::SimplePDPEndpointsSecure* secure_endpoints = nullptr;
bool is_secure = mp_RTPSParticipant->is_secure();
if (is_secure)
{
secure_endpoints = new fastdds::rtps::SimplePDPEndpointsSecure();
secure_endpoints->secure_reader.listener_.reset(new PDPListener(this));
endpoints = secure_endpoints;
endpoints->reader.listener_.reset(new PDPSecurityInitiatorListener(this,
[this](const ParticipantProxyData& participant_data)
{
match_pdp_remote_endpoints(participant_data, false, true);
}));
}
else
#endif // HAVE_SECURITY
------
#if HAVE_SECURITY
if (ret_val && is_secure)
{
create_dcps_participant_secure_endpoints();
}
#endif // HAVE_SECURITY
------
}
主要是设置了一些回调,同时创建了PDPSecure的Writer和Reader
ini
bool PDPSimple::create_dcps_participant_secure_endpoints()
{
const RTPSParticipantAttributes& pattr = mp_RTPSParticipant->get_attributes();
const RTPSParticipantAllocationAttributes& allocation = pattr.allocation;
const BuiltinAttributes& builtin_att = mp_builtin->m_att;
auto endpoints = dynamic_cast<fastdds::rtps::SimplePDPEndpointsSecure*>(builtin_endpoints_.get());
assert(nullptr != endpoints);
constexpr const char* topic_name = "DCPSParticipantsSecure";
const EntityId_t reader_entity_id = c_EntityId_spdp_reliable_participant_secure_reader;
const EntityId_t writer_entity_id = c_EntityId_spdp_reliable_participant_secure_writer;
// BUILTIN DCPSParticipantsSecure READER
auto& reader = endpoints->secure_reader;
HistoryAttributes hatt;
hatt = pdp_reader_history_attributes(builtin_att, allocation);
PoolConfig reader_pool_cfg = PoolConfig::from_history_attributes(hatt);
reader.payload_pool_ = TopicPayloadPoolRegistry::get(topic_name, reader_pool_cfg);
reader.payload_pool_->reserve_history(reader_pool_cfg, true);
reader.history_.reset(new ReaderHistory(hatt));
ReaderAttributes ratt = create_builtin_reader_attributes();
WriterAttributes watt = create_builtin_writer_attributes();
add_builtin_security_attributes(ratt, watt);
RTPSReader* rtps_reader = nullptr;
if (mp_RTPSParticipant->createReader(&rtps_reader, ratt, reader.payload_pool_, reader.history_.get(),
reader.listener_.get(), reader_entity_id, true, false))
{
reader.reader_ = dynamic_cast<StatefulReader*>(rtps_reader);
assert(nullptr != reader.reader_);
}
else
{
EPROSIMA_LOG_ERROR(RTPS_PDP, "'" << topic_name << "' builtin reader creation failed");
reader.release();
return false;
}
// SPDP BUILTIN RTPSParticipant WRITER
auto& writer = endpoints->secure_writer;
hatt = pdp_writer_history_attributes(builtin_att);
PoolConfig writer_pool_cfg = PoolConfig::from_history_attributes(hatt);
writer.payload_pool_ = TopicPayloadPoolRegistry::get(topic_name, writer_pool_cfg);
writer.payload_pool_->reserve_history(writer_pool_cfg, false);
writer.history_.reset(new WriterHistory(hatt, writer.payload_pool_));
RTPSWriter* rtps_writer = nullptr;
if (mp_RTPSParticipant->createWriter(&rtps_writer, watt, writer.history_.get(),
nullptr, writer_entity_id, true))
{
writer.writer_ = dynamic_cast<StatefulWriter*>(rtps_writer);
assert(nullptr != writer.writer_);
}
else
{
EPROSIMA_LOG_ERROR(RTPS_PDP, "'" << topic_name << "' builtin writer creation failed");
writer.release();
return false;
}
return true;
}
主要是创建了PDPSecure的Writer和Reader,创建的详细内容可以参考之前的内容。有两点需要注意,这里创建的Writer和Reader都是reliable和stateful的,就是说一般情况下,消息都是可靠的。而之前创建的普通的PDP Writer和Reader都是stateless的。
这个PDP节点和之前的PDP节点的作用是一样的。还是用于Participant的发现,只是这个发现的消息,是加了密的。

3.2创建SecurityManager的节点(Entity)
scss
bool SecurityManager::create_entities()
{
if (create_participant_stateless_message_entities())
{
if (crypto_plugin_ == nullptr || create_participant_volatile_message_secure_entities())
{
EPROSIMA_LOG_INFO(SECURITY, "Initialized security manager for participant " << participant_->getGuid());
return true;
}
delete_participant_stateless_message_entities();
}
cancel_init();
return false;
}
分别调用create_participant_stateless_message_entities和create_participant_volatile_message_secure_entities
scss
bool SecurityManager::create_participant_stateless_message_entities()
{
create_participant_stateless_message_pool();
if (create_participant_stateless_message_writer())
{
if (create_participant_stateless_message_reader())
{
return true;
}
delete_participant_stateless_message_writer();
}
delete_participant_stateless_message_pool();
return false;
}
创建了ENTITYID_P2P_BUILTIN_PARTICIPANT_STATELESS_WRITER 和 ENTITYID_P2P_BUILTIN_PARTICIPANT_STATELESS_WRITER 节点(Entity)
scss
bool SecurityManager::create_participant_volatile_message_secure_entities()
{
create_participant_volatile_message_secure_pool();
if (create_participant_volatile_message_secure_writer())
{
if (create_participant_volatile_message_secure_reader())
{
return true;
}
delete_participant_volatile_message_secure_writer();
}
delete_participant_volatile_message_secure_pool();
return false;
}
创建了ENTITYID_P2P_BUILTIN_PARTICIPANT_VOLATILE_MESSAGE_SECURE_WRITER 和ENTITYID_P2P_BUILTIN_PARTICIPANT_VOLATILE_MESSAGE_SECURE_READER的pdp 端点。

这儿有2类的SecurityManager的节点。用处各有不同。
ENTITYID_P2P_BUILTIN_PARTICIPANT_STATELESS_WRITER 和ENTITYID_P2P_BUILTIN_PARTICIPANT_STATELESS_WRITER 节点,
这类节点是为了避免一类攻击(Sequence Number 攻击)。就是针对fastdds协议的攻击。就是伪装成DataWriter发送heartbeat消息,在hearbeat中,将消息的seq number范围提高,这样DataReader 低于seq number的消息将无法收到。
3.3与安全相关的其他Entity
在EDP中也有相应的安全节点,如果discovery_protection_kind设置为需要加密,则需要使用安全节点。
sedp_builtin_publications_secure_writer
sedp_builtin_publications_secure_reader
sedp_builtin_subscriptions_secure_writer
sedp_builtin_subscriptions_secure_reader

WLP也有对应的加密的节点
c_EntityId_WriterLivelinessSecure
c_EntityId_ReaderLivelinessSecure
如果governance的liveliness_protection_kind选项设置为加密选项,则使用这两个加密节点发送和接收liveliness消息。

rust
bool EDPSimple::initEDP(
BuiltinAttributes& attributes)
{
------
//create_sedp_secure_endpoints创建了安全节点
if (mp_RTPSParticipant->is_secure() && !create_sedp_secure_endpoints())
{
EPROSIMA_LOG_ERROR(RTPS_EDP, "Problem creation SimpleEDP endpoints");
return false;
}
-----
return true;
}
EDP的安全节点在EDPSimple的initEDP中创建。
rust
bool WLP::initWL(
RTPSParticipantImpl* p)
{
------
if (retVal && p->is_secure())
{
//createSecureEndpoints创建了WLP的安全节点
retVal = createSecureEndpoints();
}
------
return retVal;
}
WLP的安全节点在WLP的initWL中创建。
3.4Auth认证相关的内容
这部分消息的发送接收,是由BuiltinParticipantStatelessMessageWriter和BuiltinParticipantStatelessMessageReader完成的。
在收到pdp消息之后处理消息的时候会调用到这个process_alive_data函数
scss
void PDPSecurityInitiatorListener::process_alive_data(
ParticipantProxyData* old_data,
ParticipantProxyData& new_data,
GUID_t& writer_guid,
RTPSReader* reader,
std::unique_lock<std::recursive_mutex>& lock)
{
if (reader->matched_writer_is_matched(writer_guid))
{
// Act as the standard PDPListener when the writer is matched.
// This will be the case for unauthenticated participants when
// allowed_unathenticated_participants is true
PDPListener::process_alive_data(old_data, new_data, writer_guid, reader, lock);
return;
}
if (old_data == nullptr)
{
auto callback_data = new_data;
reader->getMutex().unlock();
lock.unlock();
//! notify security manager in order to start handshake
bool ret = parent_pdp_->getRTPSParticipant()->security_manager().discovered_participant(callback_data);
//! Reply to the remote participant
if (ret)
{
response_cb_(callback_data);
}
// Take again the reader lock
reader->getMutex().lock();
}
}
调用到SecurityManager的discovered_participant函数
ini
bool SecurityManager::discovered_participant(
const ParticipantProxyData& participant_data)
{
------
// 省略部分判断一下1.SecurityManager有没有初始化完毕2.两个participant的安全设置是否一致 3.安全插件有没有加载完毕
SecurityException exception;
AuthenticationStatus auth_status = AUTHENTICATION_INIT;
// Create or find information
bool undiscovered = false;
DiscoveredParticipantInfo::AuthUniquePtr remote_participant_info;
// Use the information from the collection
const ParticipantProxyData* remote_participant_data = nullptr;
{
std::lock_guard<shared_mutex> _(mutex_);
auto map_ret = discovered_participants_.insert(
std::make_pair(
participant_data.guid,
std::unique_ptr<DiscoveredParticipantInfo>(
new DiscoveredParticipantInfo(
auth_status,
participant_data))));
undiscovered = map_ret.second;
remote_participant_info = map_ret.first->second->get_auth();
remote_participant_data = &map_ret.first->second->participant_data();
}
bool notify_part_authorized = false;
if (undiscovered && remote_participant_info && remote_participant_data != nullptr)
{
// Configure the timed event but do not start it
const GUID_t guid = remote_participant_data->guid;
//这个设置了一个定时器,如果没有收到reply消息,将会重发消息
remote_participant_info->event_.reset(new TimedEvent(participant_->getEventResource(),
[&, guid]() -> bool
{
resend_handshake_message_token(guid);
return true;
},
static_cast<double>(auth_handshake_props_.initial_handshake_resend_period_ms_)));
IdentityHandle* remote_identity_handle = nullptr;
// Validate remote participant.
ValidationResult_t validation_ret = authentication_plugin_->validate_remote_identity(&remote_identity_handle,
*local_identity_handle_,
remote_participant_data->identity_token_,
remote_participant_data->guid, exception);
switch (validation_ret)
{
case VALIDATION_OK:
assert(remote_identity_handle != nullptr);
auth_status = AUTHENTICATION_OK;
break;
case VALIDATION_PENDING_HANDSHAKE_REQUEST:
assert(remote_identity_handle != nullptr);
//将会发送request消息的状态
auth_status = AUTHENTICATION_REQUEST_NOT_SEND;
break;
case VALIDATION_PENDING_HANDSHAKE_MESSAGE:
assert(remote_identity_handle != nullptr);
//等待对端发送request消息的状态
auth_status = AUTHENTICATION_WAITING_REQUEST;
break;
case VALIDATION_PENDING_RETRY:
// TODO(Ricardo) Send event.
default:
on_validation_failed(*remote_participant_data, exception);
std::lock_guard<shared_mutex> _(mutex_);
// Remove created element, because authentication failed.
discovered_participants_.erase(remote_participant_data->guid);
//TODO(Ricardo) cryptograhy registration in AUTHENTICAITON_OK
return false;
}
EPROSIMA_LOG_INFO(SECURITY, "Discovered participant " << remote_participant_data->guid);
// Match entities
match_builtin_endpoints(*remote_participant_data);
// Store new remote handle.
remote_participant_info->auth_status_ = auth_status;
remote_participant_info->identity_handle_ = remote_identity_handle;
// TODO(Ricardo) Start cryptography if authentication ok in this point.
// If authentication is successful, inform user about it.
if (auth_status == AUTHENTICATION_OK)
{
//TODO(Ricardo) Shared secret on this case?
std::shared_ptr<SecretHandle> ss;
notify_part_authorized = participant_authorized(*remote_participant_data, remote_participant_info, ss);
}
}
else
{
// If cannot retrieve the authentication info pointer then return, because
// it is used in other thread.
if (!remote_participant_info)
{
return false;
}
if (remote_participant_info->auth_status_ == AUTHENTICATION_FAILED)
{
remote_participant_info->auth_status_ = AUTHENTICATION_REQUEST_NOT_SEND;
}
}
bool returnedValue = true;
if (remote_participant_info->auth_status_ == AUTHENTICATION_REQUEST_NOT_SEND)
{
// Maybe send request.
// 发送消息
returnedValue = on_process_handshake(*remote_participant_data, remote_participant_info,
MessageIdentity(), HandshakeMessageToken(), notify_part_authorized);
}
restore_discovered_participant_info(remote_participant_data->guid, remote_participant_info);
if (notify_part_authorized)
{
notify_participant_authorized(*remote_participant_data);
}
return returnedValue;
}
SecurityManager对于安全的信息,存在了一个队列中
std::map<GUID_t, std::unique_ptr> discovered_participants_,里面存放了远端的participant的信息,包括participant的一些证书,participant所在的状态,比如participant 发送认证的消息之后所处的状态(具体见下面的图)
这个函数主要干了这几件事:
1.将收到的远端participant 信息存储到本地的一个map中。
2.如果这个participant是首次发现,进入认证(AUTH)的状态机。发送或者等待接收消息。
大概的流程见下图:
ParticipantAParticipantB1.ParticipantA向ParticipantB发送GMCLASSID_SECURITY_AUTH_HANDSHAKE消息2.ParticipantB收到ParticipantA发送的消息,解析消息,验证身份,向ParticipantA发送reply消息3.ParticipantA收到ParticipantB发送的Reply消息,解析消息,验证身份ParticipantAParticipantB
1.ParticipantA的BuiltinParticipantStatelessMessageWriter向ParticipantB的BuiltinParticipantStatelessMessageReader发送GMCLASSID_SECURITY_AUTH_HANDSHAKE消息。 2.ParticipantB的BuiltinParticipantStatelessMessageReader收到ParticipantA发送的GMCLASSID_SECURITY_AUTH_HANDSHAKE消息,解析消息,验证身份,向ParticipantA发送reply消息。 3.ParticipantA收到ParticipantB发送的Reply消息,解析消息,验证身份。
由于发送消息的是StatelessMessageWriter,接收的消息都是StatelessMessageReader。在第一个步骤中,ParticipantA会启动一个定时器,如果在指定的时间内没有收到ParticipantB的消息,会重新发送一条GMCLASSID_SECURITY_AUTH_HANDSHAKE消息。
如果在步骤3中,验证身份没有通过,则需要重新发送GMCLASSID_SECURITY_AUTH_HANDSHAKE消息。
整个过程是2.2节的一部份,这里面因篇幅所限,只对整个流程做一个简单介绍,至于DH算法部分的内容,这块不详细介绍了。
3.5 密钥交换
密钥交换,主要是在BuiltinParticipantVolatileMessageSecureWriter 和BuiltinParticipantVolatileMessageSecureReader之间进行。
主要发送和接收的是VolatileMessage,由于密钥消息的特殊性,所以这两个Writer和Reader是Stateful的,他们消息传输时可靠的,同时消息是VOLATILE,就是只发给当时在线的participant。
主要包括:
participantcryptotokens
writercryptotokens
readercryptotokens
这3类。
participantcryptotokens主要是为了加密解密,整个rtps消息。
writercryptotokens主要是为了加密解密writer发送的子消息(submessage)。
readercryptotokens主要是为了加密解密reader发送的子消息(比如ack消息)。

这里以participantcryptotokens举例,看一下如何交换participantcryptotokens的。
SecurityManager::discovered_participant 如果验证通过会调用SecurityManager::participant_authorized
scss
bool SecurityManager::participant_authorized(
const ParticipantProxyData& participant_data,
const DiscoveredParticipantInfo::AuthUniquePtr& remote_participant_info,
std::shared_ptr<SecretHandle>& shared_secret_handle)
{
------
//省略掉部分permission相关代码
-------
if (access_plugin_ == nullptr || remote_permissions != nullptr)
{
------
if (crypto_plugin_ != nullptr)
{
-------
// Starts cryptography mechanism
// 存储远端的认证相关的信息,包括shared_secret
std::shared_ptr<ParticipantCryptoHandle> participant_crypto_handle =
register_and_match_crypto_endpoint(*remote_participant_info->identity_handle_,
*shared_secret_handle);
// Store cryptography info
if (participant_crypto_handle && !participant_crypto_handle->nil())
{
std::lock_guard<shared_mutex> _(mutex_);
// Check there is a pending crypto message.
// 查一下队列中有没有之前收到的信息
auto pending = remote_participant_pending_messages_.find(participant_data.guid);
if (pending != remote_participant_pending_messages_.end())
{//如果原来有,存储原来的信息
if (!crypto_plugin_->cryptokeyexchange()->set_remote_participant_crypto_tokens(
*local_participant_crypto_handle_,
*participant_crypto_handle,
pending->second,
exception))
{
EPROSIMA_LOG_ERROR(SECURITY, "Cannot set remote participant crypto tokens ("
<< participant_data.guid << ") - (" << exception.what() << ")");
}
remote_participant_pending_messages_.erase(pending);
}
------
}
------
//匹配远端的BuiltinParticipantVolatileMessageSecureWriter 和BuiltinParticipantVolatileMessageSecureReader
match_builtin_key_exchange_endpoints(participant_data);
//交换密钥
exchange_participant_crypto(participant_crypto_handle, participant_data.guid);
}
else
{
{
shared_lock<shared_mutex> _(mutex_);
// Store shared_secret.
auto dp_it = discovered_participants_.find(participant_data.guid);
if (dp_it != discovered_participants_.end())
{
dp_it->second->set_shared_secret(shared_secret_handle);
dp_it->second->set_permissions_handle(remote_permissions);
}
}
match_builtin_key_exchange_endpoints(participant_data);
}
------
return true;
}
return false;
}
这个函数主要是
1.调用了register_and_match_crypto_endpoint存储了远端participant的一些认证信息(SharedSecret等)。
2.调用了exchange_participant_crypto函数,这个函数主要是生成一个local_participant_crypto_tokens,并将这个local_participant_crypto_tokens打包成消息,发送给远端的participant
scss
void SecurityManager::exchange_participant_crypto(
std::shared_ptr<ParticipantCryptoHandle> remote_participant_crypto,
const GUID_t& remote_participant_guid)
{
SecurityException exception;
// Get participant crypto tokens.
ParticipantCryptoTokenSeq local_participant_crypto_tokens;
if (crypto_plugin_->cryptokeyexchange()->create_local_participant_crypto_tokens(local_participant_crypto_tokens,
*local_participant_crypto_handle_, *remote_participant_crypto, exception))
{
ParticipantGenericMessage message = generate_participant_crypto_token_message(remote_participant_guid,
local_participant_crypto_tokens);
CacheChange_t* change = create_change_for_message(
message,
participant_volatile_message_secure_writer_history_);
if (change != nullptr)
{
// Serialize message
// 初始化aux_msg
CDRMessage_t aux_msg(0);
------
if (CDRMessage::addParticipantGenericMessage(&aux_msg, message))
{
change->serializedPayload.length = aux_msg.length;
// 放入消息队列中,发送出去
if (!participant_volatile_message_secure_writer_history_->add_change(change))
{
participant_volatile_message_secure_writer_history_->release_change(change);
EPROSIMA_LOG_ERROR(SECURITY, "WriterHistory cannot add the CacheChange_t");
}
}
else
{
participant_volatile_message_secure_writer_history_->release_change(change);
EPROSIMA_LOG_ERROR(SECURITY, "Cannot serialize ParticipantGenericMessage");
}
}
------
}
------
}
这个函数主要就是将
1.调用create_local_participant_crypto_tokens生成local_participant_crypto_tokens
2.将local_participant_crypto_tokens打包成volatilemessage传给远端的participant
scss
void SecurityManager::process_participant_volatile_message_secure(
const CacheChange_t* const change)
{
------
// Deserialize message
// 省略部分是对message的解析
------
CDRMessage::readParticipantGenericMessage(&aux_msg, message);
if (message.message_class_id().compare(GMCLASSID_SECURITY_PARTICIPANT_CRYPTO_TOKENS) == 0)
{
------
const GUID_t remote_participant_key(message.message_identity().source_guid().guidPrefix,
c_EntityId_RTPSParticipant);
std::shared_ptr<ParticipantCryptoHandle> remote_participant_crypto;
DiscoveredParticipantInfo::AuthUniquePtr remote_participant_info;
// Search remote participant crypto handle.
{
shared_lock<shared_mutex> lock(mutex_);
auto dp_it = discovered_participants_.find(remote_participant_key);
if (dp_it != discovered_participants_.end())
{
if (dp_it->second->get_participant_crypto() == nullptr)
{
return;
}
remote_participant_crypto = dp_it->second->get_participant_crypto();
remote_participant_info = dp_it->second->get_auth();
}
------
}
if (remote_participant_crypto != nullptr)
{
SecurityException exception;
if (!crypto_plugin_->cryptokeyexchange()->set_remote_participant_crypto_tokens(
*local_participant_crypto_handle_,
*remote_participant_crypto,
message.message_data(),
exception))
{
EPROSIMA_LOG_ERROR(SECURITY, "Cannot set remote participant crypto tokens ("
<< remote_participant_key << ") - (" << exception.what() << ")");
}
else
{
// Release the change from the participant_stateless_message_writer_pool_
// As both participants have already authorized each other
if (remote_participant_info &&
remote_participant_info->change_sequence_number_ != SequenceNumber_t::unknown())
{
participant_stateless_message_writer_history_->remove_change(
remote_participant_info->change_sequence_number_);
remote_participant_info->change_sequence_number_ = SequenceNumber_t::unknown();
}
}
}
else
{
std::lock_guard<shared_mutex> _(mutex_);
remote_participant_pending_messages_.emplace(remote_participant_key, std::move(message.message_data()));
}
if (remote_participant_info)
{
restore_discovered_participant_info(remote_participant_key, remote_participant_info);
}
}
else if (message.message_class_id().compare(GMCLASSID_SECURITY_READER_CRYPTO_TOKENS) == 0)
{
//这部分是对writercryptotokens的处理
------
}
else if (message.message_class_id().compare(GMCLASSID_SECURITY_WRITER_CRYPTO_TOKENS) == 0)
{
//这部分是对readercryptotokens的处理
------
}
------
}
这个函数就是收到volatile_message,处理这些消息,在statefulreader的change_received 函数中会调用listener的on_new_change_added函数,最终调用到process_participant_volatile_message_secure函数。
这些消息又分为三类:
participantcryptotokens相关的消息
writercryptotokens相关的消息
readercryptotokens相关的消息
这些消息分别处理
最终将会调用set_remote_participant_crypto_tokens,将远端的participant_crypto存储起来。
1.ParticipantA收到ParticipantB发送PDP消息,ParticipantA发现了ParticipantB
2.Auth插件认证通过(可以参看3.3auth认证部分)通过SecurityManager的安全认证其实比较复杂,有多个步骤,可以看一下3.3部分的内容。
3.ParticipantA调用register_matched_remote_participant存储相关的ParticipantB的信息(SharedSecret等)
4.ParticipantA调用exchange_participant_crypto函数,这个函数调用create_local_participant_crypto_tokens,获取participantcryptotokens。将local_participant_crypto_tokens打包成volatilemessage,发送到ParticipantB(也就是第5步)。
5.ParticipantA将participantcryptotokens 发送给ParticipantB
6.ParticipantB调用process_participant_volatile_message_secure,处理这个消息,调用set_remote_participant_crypto_tokens将这个participant_crypto_tokens保存到本地。
3.6消息的加密解密
RTPSMessageGroup的send函数,调用SecurityManager的encode_rtps_message函数来对rtps消息进行加密。
c
bool SecurityManager::encode_rtps_message(
const CDRMessage_t& input_message,
CDRMessage_t& output_message,
const std::vector<GuidPrefix_t>& receiving_list) const
{
------
std::vector<std::shared_ptr<ParticipantCryptoHandle>> receiving_crypto_list;
for (const auto& remote_participant : receiving_list)
{
const GUID_t remote_participant_key(remote_participant, c_EntityId_RTPSParticipant);
if (remote_participant_key == participant_->getGuid())
{
receiving_crypto_list.push_back(local_participant_crypto_handle_);
}
else
{
auto dp_it = discovered_participants_.find(remote_participant_key);
if (dp_it != discovered_participants_.cend() && dp_it->second->get_participant_crypto() != nullptr)
{
receiving_crypto_list.push_back(dp_it->second->get_participant_crypto());
}
------
}
}
SecurityException exception;
return crypto_plugin_->cryptotransform()->encode_rtps_message(output_message,
input_message, *local_participant_crypto_handle_, receiving_crypto_list,
exception);
}
receiving_crypto_list中存储的就是各个participant对应的participant_crypto,然后调用插件,对消息,根据participant的不同加密。
ini
int SecurityManager::decode_rtps_message(
const CDRMessage_t& message,
CDRMessage_t& out_message,
const GuidPrefix_t& remote_participant) const
{
------
// Init output buffer
CDRMessage::initCDRMsg(&out_message);
std::shared_ptr<const ParticipantCryptoHandle> remote_participant_crypto_handle;
const GUID_t remote_participant_key(remote_participant, c_EntityId_RTPSParticipant);
if (remote_participant_key == participant_->getGuid())
{
remote_participant_crypto_handle = local_participant_crypto_handle_;
}
else
{
shared_lock<shared_mutex> _(mutex_);
auto dp_it = discovered_participants_.find(remote_participant_key);
if (dp_it != discovered_participants_.end())
{
remote_participant_crypto_handle = dp_it->second->get_participant_crypto();
}
}
int returnedValue = -1;
if (remote_participant_crypto_handle != nullptr)
{
SecurityException exception;
bool ret = crypto_plugin_->cryptotransform()->decode_rtps_message(out_message,
message,
*local_participant_crypto_handle_,
*remote_participant_crypto_handle,
exception);
------
}
else
{
------
}
return returnedValue;
}
找到远端participant的remote_participant_crypto_handle,和本地participant的local_participant_crypto_handle_一起将message解密出来。
这里仅仅是将使用participant_crypto的加密解密rtps消息的情况进行了解读,使用writercryptotokens对writer发送的submessage加密解密的情况和使用readercryptotokens对reader发送的submessage进行加密解密的情况这块不再详细解读了,读者可以根据源码对这块进行详细解析。