DDS通信中间件——DDS安全规范

DDS通信中间件——DDS安全规范

做了十年DDS通信中间件产品的程序员和大家分享一下对DDS这套规范的个人理解。预期本系列文章将包括以下内容陆续更新:

  1. DDS规范概述
  2. DCPS规范解读 & QoS策略
  3. XTypes规范解读
  4. RTPS规范解读
  5. DDS安全规范解读
  6. DDS-RPC规范解读
  7. DDS-TSN规范解读
  8. DDS-XRCE规范解读

1. 概述

1.1. DDS安全特点

很多时候我们向客户介绍DDS是开放式的,应用程序之间是松耦合的,发布方和接收方甚至不需要感知到对方的存在,客户第一反应会是:“这样会不会不安全啊?要是有人乱发布/订阅怎么办?数据怎么保证安全?”。基于这些诉求,OMG组织在2016年提出了DDS-Security规范,这份规范提出了完整、细粒度的机制来保证基于DDS的分布式系统中的数据安全,这份协议完整定义了包括:身份验证、访问控制、数据加密等安全插件的功能逻辑,并提供二次开发接口,允许用户实现自定义的安全插件,并且针对常用的安全需求提供了预置的几个插件定义,实现开箱即用。DDS安全规范是DDS区别其他中间件的一个显著特点,其特征如下:

  • 应用层的安全机制,不依赖特定的底层传输协议,类似于SOME/IP如果需要数据安全传输,只能依赖底层的TLS/DTLS。
  • 细粒度的权限控制,权限的控制可以在:应用、域、主题发布、主题订阅、特定实例数据;
  • 灵活的加密控制,在相同的信道上,针对不同的主题数据可以选择多种加密方式,包括:不加密、签名、完整加密;
  • 支持自定义加密策略,支持二次开发适合自身场景的加密插件。

1.2. DDS安全使用场景

  • 个人认为DDS安全规范在私有网络中(物理隔离)的使用意义不大,例如:一部雷达内部、一艘船上的指控网络,一般这种场景下不存在内部的恶意应用、换句话说如果私有网络已经被入侵了的话,在DDS上再搞DDS安全的意义不大,当然在这种场景下,可以由总体部门来设计DDS安全策略,为每个分系统分配权限,以防止配套的分系统写错或者订阅/发布无关的主题。

  • DDS安全规范在需要接入共有网络,尤其是无线网络场景下是非常有必要的,例如:未来车-车协同、车-路协同中,不可避免的需要将车辆自身的一些敏感信息发布到网络中或者从路测获取一些场景信息,此时对于发送端/接收端的身份认证就非常有必要。

1.3. DDS安全规范纲要

DDS-Security规范目前最新版本是2018年发布的1.1版本,规范相关的资料页面参考,DDS安全规范的主要内容参见如下的思维导图,规范大部分内容是安全协议的实现细节,由于大部分人员不是DDS产品开发人员而是使用人员,并且大部分读者不需要自行开发自定义的安全插件而只需要使用内置插件获取开发好的插件,所以这篇重点介绍内置插件的使用流程以及实例,简要介绍实现的原理,如有兴趣私信讨论。

在这里插入图片描述

2. 需求分析

安全需求中涉及以下几个角色:

  • 张三:DDS域参与者,授权发布主题名为Demo的主题数据;
  • 李四:DDS域参与者,授权订阅主题名为Demo的主题数据;
  • 王五:一个窃听者,他没有被授权订阅Demo主题数据,但是他和其他人连接在同一网络,试图查看数据;
  • 赵六:一个入侵者,他没有被授权发布Demo主题数据,但是他和其他人连接在同一网络,试图发送数据;
  • 孙七:恶意的DDS域参与组合,他被授权订阅主题 Demo主题数据,但他未被授权发布主题Demo的数据。但是,孙七尝试订阅数据获得的信息并发布,并试图说李四他是合法的发布者;
  • 周八:是一个需要接收和发送有关Demo主题数据的受信任服务。例如:持久性服务或中继服务。他被信任可以无恶意地中继信息。但不被信任可以查看信息的内容;

2.1. 未授权的订阅

王五与其他角色连接到相同的网络基础架构,尽管这些消息并非发送给王五,但他能够观察网络数据包:例如:

  • 王五可以使用DDS订阅Demo主题,在这种情况下需要张三在匹配通信前验证对方的身份是被授权过的;
  • 王五可以观察通信信道,例如:抓包,或者在张三和李四通过多播进行通信的情况下,王五可以简单地侦听相同的多播地址,在这种情况下张三需使用仅与李四、赵六等授权接收者共享的密钥加密发送的数据。

2.2. 未授权的发布

赵六与其他角色连接到相同的网络基础架构,赵六可以通过以下方式注入数据:

  • 赵六恶意使用DDS发布Demo主题,在这种情况下需要李四在接收数据之前验证对方的身份是被授权过的;
  • 赵六伪造张三的报文头,并篡改数据发送到李四的地址+端口,在这种情况下需要张三发送的数据包含基于共享密码哈希的消息认证码 (HMAC) 或数字签名;

2.2. 伪造响应

假设张三使用HMAC方式来标识自己的数据,由于孙七是合法的订阅者,所以张三会把用于HMAC的密码分享给孙七,孙七拿到这个密码之后,他可以使用这个密码来生成HMAC假装自己是张三在发送数据。这种情况下需要:

  • 细化权限,对于相同的主题发布以及订阅的权限需要单独控制;
  • 张三给每个接收者共享的密码需要不同;

2.3. 基础设施监听

周八是DDS通用服务,通用服务需要能够了解RTPS报文头以及子消息的元数据,但是不需要了解负载数据,这个需求需要能够分粒度加密RTPS的不同部分,例如:仅加密负载数据、加密子消息、加密整个RTPS报文。

3. DDS&RTPS扩展

3.1. 基本数据结构扩展

为支持DDS安全框架,该协议扩展了DDS以及RTPS协议,详细描述参见下表。

类型扩展协议说明
Property_tDCPS规范(用户可见) & RTPS规范<字符串,字符串>格式的键值对用来存储数据
BinaryProperty_tDCPS规范(用户可见) & RTPS规范<字符串,二进制缓冲区>格式的键值对用来存储数据
DataHolderRTPS规范包括一个字符串的类别、Property_t数组以及BinaryProperty_t数组来存储各种透明数据,这个数据结构是后续所有数据结构的基础,后续其他扩展的类型都是这个数据结构的变体,要么直接是这个结构的别名,要么是这个数据结构的组合。
TokenRTPS规范DataHolder的别名,在不同的安全插件之间传递的类型是不同类型的Token。

类图参见下图。

在这里插入图片描述

3.2. QoS扩展

通过扩展实体QoS的成员用于配置DDS安全,具体的扩展如下:

QoS扩展成员说明
PropertyQosPolicy新增类型包含Property_t序列以及BinaryProperty_t序列的结构体。
DomainParticipantQosPropertyQosPolicy类型的property成员扩展在域参与者上的属性QoS
DataWriterQosPropertyQosPolicy类型的property成员扩展在数据写者上的属性QoS
DataReaderQosPropertyQosPolicy类型的property成员扩展在数据读者上的属性QoS

3.3. 内置主题扩展

为交换DDS安全相关的信息,扩展RTPS简单发现协议中定义的内置主题以及新定义其他内置主题和实体,详细描述如下:

内置主题说明
扩展DCPSParticipants复用原有的主题,扩展关联的数据类型,包括:增加身份验证令牌(Token)、权限令牌、其他QoS属性成员
新增DCPSPublicationsSecure类比原有的DCPSPublication用于交换非敏感的主题信息,新增的这个主题用于交换敏感的主题信息,在原有的数据结构上扩展新增数据标签成员。
新增DCPSSubscriptionsSecure类比原有的DCPSPublication用于交换非敏感的主题信息,新增的这个主题用于交换敏感的主题信息,在原有的数据结构上扩展新增数据标签成员。
新增DCPSParticipantMessageSecure类比原有的DCPSParticipantMessage用于维护非敏感主题的存活性信息,新增的这个主题用于维持敏感主题的存活性信息。
新增DCPSParticipantStatelessMessage用于解决“序列号攻击”,即在在执行相互认证和密钥交换之前,可能被伪造的非常大的序列号攻击导致忽略正常的序列号报文,通过在主题数据内容中添加随机序列号来解决。
新增DCPSParticipantVolatileMessageSecure用于在实体之间执行密钥交换。

3.4. RTPS报文扩展

子消息扩展,结构如下图,简要描述如下表:

子消息说明
SecureSubMsg包裹一个或者多个经过加密、数字签名处理过的正常的RTPS子消息,与普通的Submessage结构相同,一个消息头+多个子消息元素。
SecurePrefixSubMsg加密RTPS子消息前缀,包括处理算法信息。
SecurePostfixSubMsg加密RTPS子消息后缀,包括处理结果信息。
SecureRTPSPrefixSubMsg加密RTPS消息前缀,包括处理算法信息。
SecureRTPSPostfixSubMsg加密RTPS消息后缀,包括处理结果信息。
SecureBody承载加密消息体。

在这里插入图片描述

如下所示,左边为需要传递的原始RTPS消息,右边为加密后的RTPS消息,其中第一个子消息整个都被加密,第二子消息仅负载被加密。

在这里插入图片描述

4. 安全框架

安全框架定义了几类插件应该实现的接口,并按照一定的流程调用抽象接口从而完成插件功能,用户可以实现插件定义的接口功能实现自定义的DDS安全功能。

4.1. 身份验证插件

接口说明
validate_local_identity根据参数中提供的信息验证本地域参与者的身份信息是否有效,比如:证书是否合法(伪造、过期)。
validate_remote_identity根据参数中提供的信息验证发现的远程域参与者的身份的过程。
begin_handshake_request当通过身份验证后,使用该操作发起握手,握手通常用于交换密钥等信息。
begin_handshake_reply响应握手请求。
process_handshake处理握手。
get_shaked_secret获取共享的密码。
……其他获取信息以及资源回收的接口。

4.2. 访问控制插件

接口说明
validate_local_permissions根据参数中提供的信息验证本地域参与者的权值配置是否有效,比如:文件是否合法(伪造、过期)。
validate_remote_permissions验证远程的权限配置是否有效,如有效则保存远程的权限。
check_create_*检查本地是否有权限创建“域参与者、数据写者、数据读者、主题”,如果没有则本地无法创建成功。
check_local_datawriter_*_instance检查本地是否有权限注册/注销某个实例,提供基于主题更细粒度的控制。
check_remote_*检查远程是否有权限创建“域参与者、数据写者、数据读者、主题”,如果没有权限则会忽略这个实体的匹配。
check_local_datawriter/reader_match基于远程实体的数据标签判断是否应该与之匹配。
check_remote_datawriter_*_instance检查远程是否有权限注册/注销某个实例,提供基于主题更细粒度的控制。
……其他获取信息以及资源回收的接口。

4.3. 数据加密插件

接口说明
register_local_participant注册一个本地的域参与者加密句柄。
register_matched_remote_participant为远端的域参与者注册一个加密句柄。
register_local_datareader注册一个本地的数据读者的加密句柄。
register_local_datawriter注册一个本地的数据写者的加密句柄。
register_matched_remote_datareader为远端的数据读者注册一个加密句柄
register_matched_remote_datawriter为远端的数据写者注册一个加密句柄
unregister_*注销加密句柄。
create_local_participant/datawriter/datareader_crypto_tokens创建本地域参与者/数据写者/数据读者需要传输的密钥信息。
set_remote_participant/datawriter/datareader_crypto_tokens设置通过信息交换得到的远端域参与者/数据写者/数据读者密钥信息。
return_crypto_tokens释放交换信息使用的空间。

5. 内置插件

为了方便用户开箱即用,DDS安全规范定义了几个内置插件,这些插件能够满足大部分的安全需求场景,有内置插件后用户无需按照框架规定的接口来从头实现安全插件。

5.1. DDS:Auth-PKI-DH

使用CA证书验证实体的身份并通过D-H密钥交换算法交换密码。

5.2. DDS:Access:Permissions

利用权限配置文件的形式对主题进行访问控制,配置文件由CA签名以防止伪造或者篡改。

5.3. DDS:Crypto:AES-GCM-GMAC

使用AES-GCM加密算法对不同层次的报文进行加密/签名,包括用户数据、RTPS子消息、RTPS消息。

5.4. DDS:Logging:DDS_LogTopic

通过内置DDS:Security:LogTopic主题发布日志信息,该主题的数据需经过身份验证、签名和加密以防伪造和篡改。

6. 内置插件使用流程

这里以ZRDDS为示例展示内置插件的使用,完整的详细的示例可私信联系。

6.1. 基本概念

  • OpenSSL 是一个安全套接字层密码库,囊括主要的密码算法、常用密钥、证书封装管理功能及实现ssl协议。OpenSSL整个软件包大概可以分成三个主要的功能部分:SSL协议库libssl、应用程序命令工具以及密码算法库libcrypto。
  • .KEY 通常指私钥。
  • .CSR 是 Certificate Signing Request 的缩写,即证书签名请求,这不是证书,只是包含申请证书的基本信息。生成证书时要把这个提交给权威的证书颁发机构,颁发机构审核通过之后,再根据这些申请信息生成相应的证书。
  • .CRT 即 certificate的缩写,即证书。
  • X.509 是一种证书格式。对X.509证书来说,认证者总是CA或由CA指定的人,一份X.509证书是一些标准字段的集合,这些字段包含有关用户或设备及其相应公钥的信息。
  • X.509的证书文件,一般以.crt结尾,根据该文件的内容编码格式,可以分为以下二种格式:
  • .PEM - Privacy Enhanced Mail,打开看文本格式,以"-----BEGIN…“开头,”-----END…"结尾,内容是 BASE64 编码。Apache 和 *NIX 服务器偏向于使用这种编码格式。
  • .DER - Distinguished Encoding Rules,打开看是二进制格式,不可读。Java 和 Windows 服务器偏向于使用这种编码格式。

6.2. 配置CA

  • 切换到将要存储CA的目录,创建CA工作目录并进入,例如cd ~ && mkdir demoCA && cd demoCA,demoCA可替换为其他名称;
  • 拷贝openssl配置文件到demoCA目录,Windows为Openssl安装目录/bin/openssl.cnf,Linux上为/etc/ssl/openssl.cnf,修改并确认文件内容;
dir             = .                     # Where everything is kept
certs           = $dir/certs            # Where the issued certs are kept
crl_dir         = $dir/crl              # Where the issued crl are kept
database        = $dir/index.txt        # database index file.
new_certs_dir   = $dir/newcerts         # default place for new certs.
certificate     = $dir/cacert.pem       # The CA certificate
serial          = $dir/serial           # The current serial number
crlnumber       = $dir/crlnumber        # the current crl number
crl             = $dir/crl.pem          # The current CRL
private_key     = $dir/private/cakey.pem# The private key
RANDFILE        = $dir/private/.rand    # private random number file
default_days= 365                # how long to certify for
default_crl_days= 30             # how long before next CRL
default_md= md5                  # which message digest to use
preserve= no                     # keep passed DN ordering
  • 使用命令mkdir ca && mkdir user && mkdir newcerts && touch index.txt && touch index.txt.attr && touch serial && echo 01 >> serial创建CA所需文件或者目录结构;

    • ca目录存储ca的证书私钥等;
    • user目录存储用户证书请求文件等;
    • newcerts中存储为用户生成的证书;
  • 创建私钥:openssl genrsa -out ca/democa.key 2048

    • genrsa表示生成rsa算法生成密钥的子命令;
    • [可选] -des3,为私钥文件设置密码,后续使用密钥时需要密码验证;
    • -out指定生成的私钥文件名称;
    • 2048表示加密长度,建议2048
  • 生成证书请求文件(CSR):openssl req -new -key ca/democa.key -subj "/C=CN/ST=JS/L=NJ/O=ZR/CN=demoCA" -out ca/democa.csr

    • req表示产生证书签发申请子命令;
    • -new 新的证书申请;
    • -signkey 签名密钥(key)文件,由第一步生成。
    • -out 输出为 CSR 文件,这是一个请求文件。
    • -subj 主体名称,包括以下信息:
      • C 国家
      • ST 省份
      • L 市
      • O 机构
      • OU 部门
      • CN (Common Name) 一般是域名
      • EmailAddress 邮箱
  • 自签署证书(正常申请证书是你把上面生成的请求文件(.CSR)发送给可信机构(CA),让可信机构根据你的请求去生成和签署证书,再给你发回来。这里是CA自签署。):openssl x509 -req -sha1 -in ca/democa.csr -signkey ca/democa.key -days 3650 -out ca/democa.pem

    • x509为签发X.509格式证书子命令;
    • -req 证书输入请求;
    • -days 证书有效天数;
    • -in 输入文件,这里是上一步生成的请求文件(.CSR);
    • -signkey 签名密钥(key)文件,由第一步生成;
    • -out 输出文件,生成证书文件(.pem)。

6.3. 签发证书

为需要使用安全的应用签发证书。

  • 以下命令可以在需要申请证书的节点上运行:
    • 创建私钥:openssl genrsa -out user/app1.key 2048
    • 生成证书请求文件:openssl req -new -key user/app1.key -subj "/C=CN/ST=JS/O=ZR/CN=app1" -out user/app1.csr
      • -subj标识证书的主体,后续在配置文件中会需要使用,注意:由于实现原因,ZRDDS内置插件主体名称不使用/L字段。
  • 在CA目录运行以下命令为app1颁发证书:
    • 使用根证书签发应用的证书:openssl ca -cert ./ca/democa.pem -keyfile ./ca/democa.key -config ./openssl.cnf -in user/app1.csr -out user/app1Cert.pem

6.4. 编写DDS安全相关的配置文件

6.4.1. 权限配置文件

权限配置文件主要用于描述本地的访问控制权限,格式为XML,根节点标签名称为dds,类型为PermissionNode,其Schema结构如下。

类型属性/子元素说明
PermissionsNodepermissions出现一次的子元素,类型为Permissions
Permissionsgrant出现一次及以上的子元素,类型为Grant
Grantname出现一次的属性,用于标识该规则
^subject_name出现一次的子元素,指定该条配置针对的对象,subject_name是申请证书的主体,通常每个应用或者每个域参与者唯一标识;
^validity出现一次的子元素,类型为Validity,表明该配置的有效期范围,当前时间不在有效期范围内的规则将被忽略。
^allow_rule出现0到多次的子元素,类型为Rule,表示一组允许访问规则。
^deny_rule出现0到多次的子元素,类型为Rule,表示一组拒绝访问规则。
^default出现1次的子元素,类型为枚举字符串,DENY表示拒绝,ALLOW表示允许,表示未在allow_rule以及deny_rule中定义的域号、主题、分区等的默认行为。
Validitynot_before出现一次的子元素,用于指定开始时间,形式为YYYY-MM-DDThh:mm::ss。
^not_after出现一次的子元素,用于指定结束时间,格式与not_before相同。
RuleDomainIdSet出现一次的子元素,用于表明规则中的域号集合。
^publish出现0到多次的子元素,类型为Criteria,用于描述发布相关的权限。
^subscribe出现0到多次的子元素,类型为Criteria,用于描述订阅相关的权限。
^relay出现0到多次的子元素,类型为Criteria,用于描述发布订阅双向相关的权限。
DomainIdSetid出现0到多次的子元素,类型为DomainId,每个元素代表集合中的一个域号。
^id_range出现0到多次的子元素,类型为DomainIdRange,每个元素代表一个连续的域号区间。
DomainIdRangemin出现一次的子元素,代表区间下界。
^max出现一次的子元素,代表区间上界。
DomainId非负整数表示域号。
Criteriatopics出现一次的子元素,类型为TopicExpressionList,用于指定主题列表,取决于上层规则,表示允许/拒绝该主题列表。
^partitions出现一次的子元素,类型为PartitionExpressionList,用于指定分区列表,取决于上层规则,表示允许/拒绝加入这些分区列表。
TopicExpressionListtopic出现1次到多次的子元素,类型为TopicExpression。
TopicExpression主题名的表达式字符串,支持POSIX 1003.2-1992的正则表达式。
PartitionExpressionListpartition出现1次到多次的子元素,类型为PartitionExpression。
PartitionExpression分区名的表达式字符串,支持POSIX 1003.2-1992的正则表达式。
6.4.2. 管理配置文件

管理配置文件用于描述如何处理与远程实体的发现以及数据加密配置,格式为XML,根节点标签名称为dds,类型为DomainAccessRulesNode,其Schema格式如下:

类型属性/子元素说明
DomainAccessRulesNodedomain_access_rules出现一次的子元素,类型为DomainAccessRules
DomainAccessRulesdomain_rule出现一次及以上的子元素,类型为DomainRule
DomainRuledomains出现一次及以上的子元素,类型为DomainIdSet,用于描述该规则适用的域集合,该类型的描述参见权限配置文件描述。
^allow_unauthenticated_participants出现一次的子元素,表明是否允许匹配未授权的域参与者,true表示允许,false表示不允许。
^enable_join_access_control出现一次的子元素,表明在验证完成后,是否需要进一步检查远程域参与者的权限,true表示需要检查,false表示不需要检查。
^discovery_protection_kind出现一次的子元素,枚举类型表示内置发现信息的保护级别,ENCRYPT表示加密、SIGN表示签名、NONE表示不额外保护。
^liveliness_protection_kind出现一次的子元素,枚举类型表示内置心跳发现信息的保护级别,ENCRYPT表示加密、SIGN表示签名、NONE表示不额外保护。
^rtps_protection_kind出现一次的子元素,枚举类型表示RTPS整个消息的保护级别,ENCRYPT表示加密、SIGN表示签名、NONE表示不额外保护。
^topic_access_rules出现一次的子元素,类型为TopicAccessRules。
TopicAccessRulestopic_rule出现一次及以上的子元素,类型为TopicRule,每个元素针对一个或一组主题名的规则。
TopicRuletopic_expression出现一次的子元素,主题名的表达式字符串,支持POSIX 1003.2-1992的正则表达式。
^enable_discovery_protection针对该主题是否启动内置信息保护。
^enable_read_access_control出现一次的子元素,表示是否需要检查权限配置文件中的订阅权限配置。
^enable_write_access_control出现一次的子元素,表示是否需要检查权限配置文件中的发布权限配置。
^metadata_protection_kind出现一次的子元素,枚举类型表示主题相关RTPS子消息的保护级别,ENCRYPT表示加密、SIGN表示签名、NONE表示不额外保护。
^data_protection_kind出现一次的子元素,枚举类型表示主题负载数据保护级别,ENCRYPT表示加密、SIGN表示签名、NONE表示不额外保护。

6.5. 配置文件签名

为保证配置文件的不可篡改性,需使用CA对配置文件进行签名。

  • 签名权限配置文件为permission.xml: openssl smime -sign -signer ca/democa.pem -inkey ca/democa.key -in permission.xml -out permission.smime
    • -sign表示签名;
    • -signer表示用于签名的证书;
    • -inkey表示与签名证书匹配的私钥;
    • -in表示需要签名的文件路径;
    • -out表示签名后的文件路径;
  • 签名管理配置文件为governance.xml: openssl smime -sign -signer ca/democa.pem -inkey ca/democa.key -in governance.xml -out governance.smime

6.6. 配置QoS并加载安全插件

安全插件表现形式为一个动态库,动态库必须提供获取/删除实例的函数,使用 DDS::DomainParticipantFactory::load_security_plugin 函数加载安全插件。

在使用安全插件时,通过 DomainparticipantQos.property对指定插件进行参数配置,内置插件配置项如下,其中属性的值支持两种形式,file前缀表示文件路径,data前缀表示文件内容:

配置键名称说明配置值(file示例)配置值(data示例)
dds.sec.auth.certificate_revocation_list[可选],由证书机构提供的失效证书列表。在列表中的证书即使未到有效期,也会被认为是非法证书。file: client.crldata:, -----BEGIN X509 CRL-----
MIIBazCB1QIBATANBgkqhki … dFo/Zs14keJ06Oug==
-----END X509 CRL-----
dds.sec.auth.identity_ca用于身份验证的CA证书。file:ca.pemdata:,-----BEGIN CERTIFICATE-----
MIIC3DCCAcQCCQCWE5x+Z … PhovK0mp2ohhRLYI0ZiyYQ==
-----END CERTIFICATE-----
dds.sec.auth.identity_certificate标识自己身份的证书,由CA颁发。file:app1Cert.pemdata:,-----BEGIN CERTIFICATE-----
MIIDjjCCAnYCCQDCEu9…6rmT87dhTo=
-----END CERTIFICATE-----
dds.sec.auth.private_key与自身证书匹配的自身私钥。file:app1.keydata:,-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA3HIh…AOBaaqSV37XBUJg==
-----END RSA PRIVATE KEY-----
dds.sec.access.permissions_ca用于访问控制的CA证书(可以与身份验证CA证书相同)。file:ca.pem
dds.sec.access.permissions使用访问控制CA签名的许可配置文件file:permission.smime
dds.sec.access.governance使用访问控制CA签名的管理配置文件file:governance.smime

7. 内置插件使用实例

7.1. QoS配置文件

三个内置插件所涉及所有QoS配置放在以下的zrdds_security_qos.xml文件中。

<zrdds_qos xmlns="http://www.omg.org/dds/">
  <qos_library name="default_lib">
    <qos_profile name="default_profile">
      <participantfactory_qos name="security_example">
        <property>
          <value />
        </property>
      </participantfactory_qos>
      <participant_qos name="auth_example_pub">
        <property>
          <value>
            <element>
              <name>dds.sec.plugin_name</name>
              <value>BuiltinPlugin</value>
              <propagate>false</propagate>
            </element>
            <element>
              <name>dds.sec.auth.identity_ca</name>
              <value>file:./config/democa.pem</value>
              <propagate>false</propagate>
            </element>
            <element>
              <name>dds.sec.auth.identity_certificate</name>
              <value>file:./config/app1Cert.pem</value>
              <propagate>false</propagate>
            </element>
            <element>
              <name>dds.sec.auth.private_key</name>
              <value>file:./config/app1.key</value>
              <propagate>false</propagate>
            </element>
            <element>
              <name>dds.sec.access.permissions_ca</name>
              <value>file:./config/democa.pem</value>
              <propagate>false</propagate>
            </element>
            <element>
              <name>dds.sec.access.permissions</name>
              <value>file:./config/auth_permission.smime</value>
              <propagate>false</propagate>
            </element>
            <element>
              <name>dds.sec.access.governance</name>
              <value>file:./config/auth_governance.smime</value>
              <propagate>false</propagate>
            </element>
          </value>
        </property>
      </participant_qos>
      <participant_qos name="auth_example_sub_auth">
        <property>
          <value>
            <element>
              <name>dds.sec.plugin_name</name>
              <value>BuiltinPlugin</value>
              <propagate>false</propagate>
            </element>
            <element>
              <name>dds.sec.auth.identity_ca</name>
              <value>file:./config/democa.pem</value>
              <propagate>false</propagate>
            </element>
            <element>
              <name>dds.sec.auth.identity_certificate</name>
              <value>file:./config/app2Cert.pem</value>
              <propagate>false</propagate>
            </element>
            <element>
              <name>dds.sec.auth.private_key</name>
              <value>file:./config/app2.key</value>
              <propagate>false</propagate>
            </element>
            <element>
              <name>dds.sec.access.permissions_ca</name>
              <value>file:./config/democa.pem</value>
              <propagate>false</propagate>
            </element>
            <element>
              <name>dds.sec.access.permissions</name>
              <value>file:./config/auth_permission.smime</value>
              <propagate>false</propagate>
            </element>
            <element>
              <name>dds.sec.access.governance</name>
              <value>file:./config/auth_governance.smime</value>
              <propagate>false</propagate>
            </element>
          </value>
        </property>
      </participant_qos>
      <participant_qos name="auth_example_sub_not_auth" />
      <participant_qos name="ac_example_pub">
        <property>
          <value>
            <element>
              <name>dds.sec.plugin_name</name>
              <value>BuiltinPlugin</value>
              <propagate>false</propagate>
            </element>
            <element>
              <name>dds.sec.auth.identity_ca</name>
              <value>file:./config/democa.pem</value>
              <propagate>false</propagate>
            </element>
            <element>
              <name>dds.sec.auth.identity_certificate</name>
              <value>file:./config/app1Cert.pem</value>
              <propagate>false</propagate>
            </element>
            <element>
              <name>dds.sec.auth.private_key</name>
              <value>file:./config/app1.key</value>
              <propagate>false</propagate>
            </element>
            <element>
              <name>dds.sec.access.permissions_ca</name>
              <value>file:./config/democa.pem</value>
              <propagate>false</propagate>
            </element>
            <element>
              <name>dds.sec.access.permissions</name>
              <value>file:./config/ac_permission.smime</value>
              <propagate>false</propagate>
            </element>
            <element>
              <name>dds.sec.access.governance</name>
              <value>file:./config/ac_governance.smime</value>
              <propagate>false</propagate>
            </element>
          </value>
        </property>
      </participant_qos>
      <participant_qos name="ac_example_sub">
        <property>
          <value>
            <element>
              <name>dds.sec.plugin_name</name>
              <value>BuiltinPlugin</value>
              <propagate>false</propagate>
            </element>
            <element>
              <name>dds.sec.auth.identity_ca</name>
              <value>file:./config/democa.pem</value>
              <propagate>false</propagate>
            </element>
            <element>
              <name>dds.sec.auth.identity_certificate</name>
              <value>file:./config/app2Cert.pem</value>
              <propagate>false</propagate>
            </element>
            <element>
              <name>dds.sec.auth.private_key</name>
              <value>file:./config/app2.key</value>
              <propagate>false</propagate>
            </element>
            <element>
              <name>dds.sec.access.permissions_ca</name>
              <value>file:./config/democa.pem</value>
              <propagate>false</propagate>
            </element>
            <element>
              <name>dds.sec.access.permissions</name>
              <value>file:./config/ac_permission.smime</value>
              <propagate>false</propagate>
            </element>
            <element>
              <name>dds.sec.access.governance</name>
              <value>file:./config/ac_governance.smime</value>
              <propagate>false</propagate>
            </element>
          </value>
        </property>
      </participant_qos>
      <participant_qos name="encry_example_pub">
        <property>
          <value>
            <element>
              <name>dds.sec.plugin_name</name>
              <value>BuiltinPlugin</value>
              <propagate>false</propagate>
            </element>
            <element>
              <name>dds.sec.auth.identity_ca</name>
              <value>file:./config/democa.pem</value>
              <propagate>false</propagate>
            </element>
            <element>
              <name>dds.sec.auth.identity_certificate</name>
              <value>file:./config/app1Cert.pem</value>
              <propagate>false</propagate>
            </element>
            <element>
              <name>dds.sec.auth.private_key</name>
              <value>file:./config/app1.key</value>
              <propagate>false</propagate>
            </element>
            <element>
              <name>dds.sec.access.permissions_ca</name>
              <value>file:./config/democa.pem</value>
              <propagate>false</propagate>
            </element>
            <element>
              <name>dds.sec.access.permissions</name>
              <value>file:./config/encry_permission.smime</value>
              <propagate>false</propagate>
            </element>
            <element>
              <name>dds.sec.access.governance</name>
              <value>file:./config/encry_governance.smime</value>
              <propagate>false</propagate>
            </element>
          </value>
        </property>
      </participant_qos>
      <participant_qos name="encry_example_sub">
        <property>
          <value>
            <element>
              <name>dds.sec.plugin_name</name>
              <value>BuiltinPlugin</value>
              <propagate>false</propagate>
            </element>
            <element>
              <name>dds.sec.auth.identity_ca</name>
              <value>file:./config/democa.pem</value>
              <propagate>false</propagate>
            </element>
            <element>
              <name>dds.sec.auth.identity_certificate</name>
              <value>file:./config/app2Cert.pem</value>
              <propagate>false</propagate>
            </element>
            <element>
              <name>dds.sec.auth.private_key</name>
              <value>file:./config/app2.key</value>
              <propagate>false</propagate>
            </element>
            <element>
              <name>dds.sec.access.permissions_ca</name>
              <value>file:./config/democa.pem</value>
              <propagate>false</propagate>
            </element>
            <element>
              <name>dds.sec.access.permissions</name>
              <value>file:./config/encry_permission.smime</value>
              <propagate>false</propagate>
            </element>
            <element>
              <name>dds.sec.access.governance</name>
              <value>file:./config/encry_governance.smime</value>
              <propagate>false</propagate>
            </element>
          </value>
        </property>
      </participant_qos>
      <datawriter_qos name="reliable">
        <reliability>
          <kind>RELIABLE_RELIABILITY_QOS</kind>
        </reliability>
      </datawriter_qos>
      <datareader_qos name="reliable">
        <reliability>
          <kind>RELIABLE_RELIABILITY_QOS</kind>
        </reliability>
      </datareader_qos>
    </qos_profile>
  </qos_library>
</zrdds_qos>

7.2. 身份验证插件

auth_example_pub中的域参与者配置了身份验证,且不允许未授权的域参与者匹配,发布主题auth_example,auth_example_sub 中有两个域参与者,其中一个配置了证书、另一个未配置证书的普通域参与者,订阅相同的主题,可以看到接收端只能订阅到一份数据。

7.2.1. 管理配置文件
<?xml version="1.0" encoding="UTF-8"?>
<dds>
    <domain_access_rules>
        <domain_rule>
        	<domains>
        		<id>150</id>
        	</domains>
            <!-- 设置不允许未认证身份的域参与者加入域。 -->
            <allow_unauthenticated_participants>FALSE</allow_unauthenticated_participants>
            <enable_join_access_control>FALSE</enable_join_access_control>
            <discovery_protection_kind>NONE</discovery_protection_kind>
            <liveliness_protection_kind>NONE</liveliness_protection_kind>
            <rtps_protection_kind>NONE</rtps_protection_kind>
            <topic_access_rules>
                <topic_rule>
                    <topic_expression>*</topic_expression>
                    <enable_discovery_protection>FALSE</enable_discovery_protection>
                    <enable_read_access_control>FALSE</enable_read_access_control>
                    <enable_write_access_control>FALSE</enable_write_access_control>
                    <metadata_protection_kind>NONE</metadata_protection_kind>
                    <data_protection_kind>NONE</data_protection_kind>
                </topic_rule>
            </topic_access_rules>
        </domain_rule>
    </domain_access_rules>
</dds>
7.2.2. 权限管理文件
<?xml version="1.0" encoding="UTF-8"?>
<dds>
    <permissions>
        <!-- 设置app1的权限,默认允许所有主题发布订阅。 -->
        <grant name="app1">
            <subject_name>C=CN,ST=JS,O=ZR,CN=app1</subject_name>
            <validity>
                <not_before>2020-10-19T10:54:00</not_before>
                <not_after>2022-10-19T10:54:00</not_after>
            </validity>
            <default>ALLOW</default>
        </grant>
        <!-- 设置app2的权限,默认允许所有主题发布订阅。 -->
        <grant name="app2">
            <subject_name>C=CN,ST=JS,O=ZR,CN=app2</subject_name>
            <validity>
                <not_before>2020-10-19T10:54:00</not_before>
                <not_after>2022-10-19T10:54:00</not_after>
            </validity>
            <default>ALLOW</default>
        </grant>
    </permissions>
</dds>
7.2.3. 发布端代码

注:这里为了减少代码量,使用简化接口,使用标准DCPS接口类似。

#include "ZRDDSCppSimpleInterface.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

using namespace DDS;

int pub(int argc, char* argv[])
{
    // 获取入口,并指明QoS配置
    DomainParticipantFactory* factory = DDSIF::Init("./config/zrdds_security_qos.xml", "security_example");
    if (factory == NULL) { printf("DDSIF::Initialize failed.\n"); return -1; }
    // 加载内置插件
    ReturnCode_t retCode = factory->load_security_plugin(
        "BuiltinPlugin", 
        "ZRDDSSecBuiltinPlugin.dll", 
        "BuiltinSecPluginGetInstance", 
        "BuiltinSecPluginFinalize");
    if (retCode != NULL)
    {
        printf("load_security_plugin failed(%d).", retCode);
        return -1;
    }
    // 初始化使用rio的域参与者
    DomainParticipant* udpDp = DDSIF::CreateDP(150, "auth_example_pub");
    if (NULL == udpDp) { printf("DDSIF::CreateDP failed.\n"); return -2; }
    // 创建数据写者
    DataWriter* rawWriter = DDSIF::PubTopic(
        udpDp, "auth_example", BytesTypeSupport::get_instance(), "reliable", NULL);
    BytesDataWriter* writer = (BytesDataWriter*)rawWriter;
    // 发送数据
    Bytes sample;
    sample.value.ensure_length(1024, 1024);
    unsigned int counter = 0;
    while (++counter < 100)
    {
        sprintf((char*)sample.value._contiguousBuffer, "sample(%u) from auth_example_pub.", counter);
        ReturnCode_t retCode = writer->write(sample, HANDLE_NIL_NATIVE);
        if (retCode != RETCODE_OK)
        {
            printf("send failed(%d).\n", retCode);
        }
        printf("send sample %s\n", sample.value._contiguousBuffer);
        ZRSleep(1000);
    }
    return 0;
}

int main(int argc, char* argv[])
{
    return pub(argc, argv);
}
7.2.4. 订阅端代码

注:这里为了减少代码量,使用简化接口,使用标准DCPS接口类似。

#include "ZRDDSCppSimpleInterface.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

using namespace DDS;

// 定义零拷贝数据类型回调接口
class BytesTypeListener :
    virtual public SimpleDataReaderListener<Bytes, BytesSeq, BytesDataReader>
{
public:
    virtual void on_process_sample(DataReader* reader, const Bytes& sample, const SampleInfo& info)
    {
        // 通过该语句获取收到的样本所属主题名称
        const char* topicName = reader->get_topicdescription()->get_name();
        // TODO 在此填写业务处理,接收端sample.userBuffer为用户负载数据,sample.userLength为用户负载长度,无需考虑预留空间
        printf("received data(%s) from topic(%s)\n", 
            sample.value.get_contiguous_buffer(), 
            topicName);
    }
};

int sub()
{
    // 获取入口,并指明QoS配置
    DomainParticipantFactory* factory = DDSIF::Init("./config/zrdds_security_qos.xml", "security_example");
    if (factory == NULL) { printf("DDSIF::Initialize failed.\n"); return -1; }
    // 加载内置插件
    ReturnCode_t retCode = factory->load_security_plugin(
        "BuiltinPlugin",
        "ZRDDSSecBuiltinPlugin.dll",
        "BuiltinSecPluginGetInstance",
        "BuiltinSecPluginFinalize");
    if (retCode != NULL)
    {
        printf("load_security_plugin failed(%d).", retCode);
        return -1;
    }
    // 初始化授权的域参与者
    DomainParticipant* authDP = DDSIF::CreateDP(150, "auth_example_sub_auth");
    if (NULL == authDP) { printf("DDSIF::CreateDP failed.\n"); return -2; }
    // 创建数据读者,并设置监听器
    BytesTypeListener* m_listener = new BytesTypeListener();
    DataReader* authDr = DDSIF::SubTopic(
        authDP, "auth_example", BytesTypeSupport::get_instance(), "reliable", m_listener);
    if (NULL == authDr) { printf("DDSIF::SubscribeTopic failed.\n"); return -5; }

    // 初始化未授权的域参与者
    DomainParticipant* unAuthDP = DDSIF::CreateDP(150, "auth_example_sub_not_auth");
    if (NULL == unAuthDP) { printf("DDSIF::CreateDP failed.\n"); return -2; }
    // 创建数据读者,并设置监听器
    //BytesTypeListener* m_listener = new BytesTypeListener();
    DataReader* noAuthDr = DDSIF::SubTopic(
        unAuthDP, "auth_example", BytesTypeSupport::get_instance(), "reliable", m_listener);
    if (NULL == noAuthDr) { printf("DDSIF::SubscribeTopic failed.\n"); return -5; }
    getchar();
    DDSIF::Finalize();
    return 0;
}

int main(int argc, char* argv[])
{
    return sub();
}

7.3. 访问控制插件

ac_example_pub中发布两个主题ac_example_1、ac_example_2,权值配置文件中配置允许发布ac_example_开头的主题,ac_example_sub中订阅两个主题,权限配置文件中配置仅允许订阅ac_example_1主题,可以看到只能成功创建ac_example_1的订阅。

7.3.1. 管理配置文件
<?xml version="1.0" encoding="UTF-8"?>
<dds>
    <domain_access_rules>
        <domain_rule>
        	<domains>
        		<id>150</id>
        	</domains>
            <allow_unauthenticated_participants>FALSE</allow_unauthenticated_participants>
            <enable_join_access_control>FALSE</enable_join_access_control>
            <discovery_protection_kind>NONE</discovery_protection_kind>
            <liveliness_protection_kind>NONE</liveliness_protection_kind>
            <rtps_protection_kind>NONE</rtps_protection_kind>
            <topic_access_rules>
                <topic_rule>
                    <!-- 设置ac_example_*主题 需要订阅/发布权限保护 -->
                    <topic_expression>ac_example_*</topic_expression>
                    <enable_discovery_protection>FALSE</enable_discovery_protection>
                    <enable_read_access_control>TRUE</enable_read_access_control>
                    <enable_write_access_control>TRUE</enable_write_access_control>
                    <metadata_protection_kind>NONE</metadata_protection_kind>
                    <data_protection_kind>NONE</data_protection_kind>
                </topic_rule>
            </topic_access_rules>
        </domain_rule>
    </domain_access_rules>
</dds>
7.3.2. 权限管理文件
<?xml version="1.0" encoding="UTF-8"?>
<dds>
    <permissions>
        <!-- 设置app1只允许在150号域内发布任意以ac_example开头的主题。 -->
        <grant name="app1">
            <subject_name>C=CN,ST=JS,O=ZR,CN=app1</subject_name>
            <validity>
                <not_before>2020-10-19T10:54:00</not_before>
                <not_after>2022-10-19T10:54:00</not_after>
            </validity>
            <allow_rule>
                <domains>
                    <id>150</id>
                </domains>
                <publish>
                    <topics>
                        <topic>ac_example*</topic>
                    </topics>
                </publish>
            </allow_rule>
            <default>DENY</default>
        </grant>
        <!-- 设置app2只允许在150号域内订阅任意以ac_example_1主题。 -->
        <grant name="app2">
            <subject_name>C=CN,ST=JS,O=ZR,CN=app2</subject_name>
            <validity>
                <not_before>2020-10-19T10:54:00</not_before>
                <not_after>2022-10-19T10:54:00</not_after>
            </validity>
            <allow_rule>
                <domains>
                    <id>150</id>
                </domains>
                <subscribe>
                    <topics>
                        <topic>ac_example_1</topic>
                    </topics>
                </subscribe>
            </allow_rule>
            <default>DENY</default>
        </grant>
    </permissions>
</dds>
7.3.3. 发布端代码

注:这里为了减少代码量,使用简化接口,使用标准DCPS接口类似。

#include "ZRDDSCppSimpleInterface.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

using namespace DDS;

int pub(int argc, char* argv[])
{
    // 获取入口,并指明QoS配置
    DomainParticipantFactory* factory = DDSIF::Init("./config/zrdds_security_qos.xml", "security_example");
    if (factory == NULL) { printf("DDSIF::Initialize failed.\n"); return -1; }
    // 加载内置插件
    ReturnCode_t retCode = factory->load_security_plugin(
        "BuiltinPlugin", 
        "ZRDDSSecBuiltinPlugin.dll", 
        "BuiltinSecPluginGetInstance", 
        "BuiltinSecPluginFinalize");
    if (retCode != NULL)
    {
        printf("load_security_plugin failed(%d).", retCode);
        return -1;
    }
    // 初始化授权的域参与者
    DomainParticipant* udpDp = DDSIF::CreateDP(150, "ac_example_pub");
    if (NULL == udpDp) { printf("DDSIF::CreateDP failed.\n"); return -2; }
    // 创建数据写者
    DataWriter* rawWriter1 = DDSIF::PubTopic(
        udpDp, "ac_example_1", BytesTypeSupport::get_instance(), "reliable", NULL);
    BytesDataWriter* acWriter1 = (BytesDataWriter*)rawWriter1;
    if (NULL == acWriter1) { printf("DDSIF::PubTopic failed.\n"); return -5; }
    DataWriter* rawWriter2 = DDSIF::PubTopic(
        udpDp, "ac_example_2", BytesTypeSupport::get_instance(), "reliable", NULL);
    BytesDataWriter* acWriter2 = (BytesDataWriter*)rawWriter2;
    if (NULL == acWriter2) { printf("DDSIF::PubTopic failed.\n"); return -5; }
    // 发送数据
    Bytes sample;
    sample.value.ensure_length(1024, 1024);
    unsigned int counter = 0;
    while (++counter < 100)
    {
        // 发送ac_example_1主题数据
        sprintf((char*)sample.value._contiguousBuffer, "sample(%u) from %s.", counter, acWriter1->get_topic()->get_name());
        ReturnCode_t retCode = acWriter1->write(sample, HANDLE_NIL_NATIVE);
        if (retCode != RETCODE_OK)
        {
            printf("send failed(%d).\n", retCode);
        }
        printf("send sample %s\n", sample.value._contiguousBuffer);
        // 发送ac_example_2主题数据
        sprintf((char*)sample.value._contiguousBuffer, "sample(%u) from %s.", counter, acWriter2->get_topic()->get_name());
        retCode = acWriter2->write(sample, HANDLE_NIL_NATIVE);
        if (retCode != RETCODE_OK)
        {
            printf("send failed(%d).\n", retCode);
        }
        printf("send sample %s\n", sample.value._contiguousBuffer);
        ZRSleep(1000);
    }
    return 0;
}

int main(int argc, char* argv[])
{
    return pub(argc, argv);
}
7.3.4. 订阅端代码

注:这里为了减少代码量,使用简化接口,使用标准DCPS接口类似。

#include "ZRDDSCppSimpleInterface.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

using namespace DDS;

// 定义零拷贝数据类型回调接口
class BytesTypeListener :
    virtual public SimpleDataReaderListener<Bytes, BytesSeq, BytesDataReader>
{
public:
    virtual void on_process_sample(DataReader* reader, const Bytes& sample, const SampleInfo& info)
    {
        // 通过该语句获取收到的样本所属主题名称
        const char* topicName = reader->get_topicdescription()->get_name();
        // TODO 在此填写业务处理,接收端sample.userBuffer为用户负载数据,sample.userLength为用户负载长度,无需考虑预留空间
        printf("received data(%s) from topic(%s)\n", 
            sample.value.get_contiguous_buffer(), 
            topicName);
    }
};

int sub()
{
    // 获取入口,并指明QoS配置
    DomainParticipantFactory* factory = DDSIF::Init("./config/zrdds_security_qos.xml", "security_example");
    if (factory == NULL) { printf("DDSIF::Initialize failed.\n"); return -1; }
    // 加载内置插件
    ReturnCode_t retCode = factory->load_security_plugin(
        "BuiltinPlugin",
        "ZRDDSSecBuiltinPlugin.dll",
        "BuiltinSecPluginGetInstance",
        "BuiltinSecPluginFinalize");
    if (retCode != NULL)
    {
        printf("load_security_plugin failed(%d).", retCode);
        return -1;
    }
    // 初始化授权的域参与者
    DomainParticipant* authDP = DDSIF::CreateDP(150, "ac_example_sub");
    if (NULL == authDP) { printf("DDSIF::CreateDP failed.\n"); return -2; }
    // 创建ac_example_1数据读者,并设置监听器,期望成功
    BytesTypeListener* m_listener = new BytesTypeListener();
    DataReader* authDr = DDSIF::SubTopic(
        authDP, "ac_example_1", BytesTypeSupport::get_instance(), "reliable", m_listener);
    if (NULL == authDr) { printf("DDSIF::SubscribeTopic failed.\n"); return -5; }
    // 创建ac_example_2数据读者,并设置监听器,由于权限配置文件中未指明能够订阅该主题,故而订阅失败
    authDr = DDSIF::SubTopic(
        authDP, "ac_example_2", BytesTypeSupport::get_instance(), "reliable", m_listener);
    if (NULL == authDr) { printf("DDSIF::SubscribeTopic ac_example_2 failed.\n"); }

    getchar();
    DDSIF::Finalize();
    return 0;
}

int main(int argc, char* argv[])
{
    return sub();
}

7.4. 数据加密插件

encry_example_pub发布encry_example_1和encry_example_2主题,在管理配置文件中配置主题encry_example_1保护级别用户负载加密,encry_example_2保护级别为空,encry_example_sub订阅对应的两个主题,使用相同的管理配置,通过抓包可以看到encry_example_1主题数据通过密文传输,encry_example_2主题数据通过明文传输。

7.4.1. 管理配置文件
<?xml version="1.0" encoding="UTF-8"?>
<dds>
    <domain_access_rules>
        <domain_rule>
        	<domains>
        		<id>150</id>
        	</domains>
            <allow_unauthenticated_participants>FALSE</allow_unauthenticated_participants>
            <enable_join_access_control>FALSE</enable_join_access_control>
            <discovery_protection_kind>NONE</discovery_protection_kind>
            <liveliness_protection_kind>NONE</liveliness_protection_kind>
            <rtps_protection_kind>NONE</rtps_protection_kind>
            <topic_access_rules>
                <topic_rule>
                    <topic_expression>encry_example_1</topic_expression>
                    <enable_discovery_protection>FALSE</enable_discovery_protection>
                    <enable_read_access_control>FALSE</enable_read_access_control>
                    <enable_write_access_control>FALSE</enable_write_access_control>
                    <metadata_protection_kind>NONE</metadata_protection_kind>
                    <!-- 设置encry_example_1主题需要主题数据加密传输 -->
                    <data_protection_kind>ENCRYPT</data_protection_kind>
                </topic_rule>
                <topic_rule>
                    <topic_expression>encry_example_2</topic_expression>
                    <enable_discovery_protection>FALSE</enable_discovery_protection>
                    <enable_read_access_control>FALSE</enable_read_access_control>
                    <enable_write_access_control>FALSE</enable_write_access_control>
                    <metadata_protection_kind>NONE</metadata_protection_kind>
                    <data_protection_kind>NONE</data_protection_kind>
                </topic_rule>
            </topic_access_rules>
        </domain_rule>
    </domain_access_rules>
</dds>
7.4.2. 权限管理文件
<?xml version="1.0" encoding="UTF-8"?>
<dds>
    <permissions>
        <grant name="app1">
            <subject_name>C=CN,ST=JS,O=ZR,CN=app1</subject_name>
            <validity>
                <not_before>2020-10-19T10:54:00</not_before>
                <not_after>2022-10-19T10:54:00</not_after>
            </validity>
            <default>ALLOW</default>
        </grant>
        <grant name="app2">
            <subject_name>C=CN,ST=JS,O=ZR,CN=app2</subject_name>
            <validity>
                <not_before>2020-10-19T10:54:00</not_before>
                <not_after>2022-10-19T10:54:00</not_after>
            </validity>
            <default>ALLOW</default>
        </grant>
    </permissions>
</dds>
7.4.3. 发布端代码

注:这里为了减少代码量,使用简化接口,使用标准DCPS接口类似。

#include "ZRDDSCppSimpleInterface.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

using namespace DDS;

int pub(int argc, char* argv[])
{
    // 获取入口,并指明QoS配置
    DomainParticipantFactory* factory = DDSIF::Init("./config/zrdds_security_qos.xml", "security_example");
    if (factory == NULL) { printf("DDSIF::Initialize failed.\n"); return -1; }
    // 加载内置插件
    ReturnCode_t retCode = factory->load_security_plugin(
        "BuiltinPlugin", 
        "ZRDDSSecBuiltinPlugin.dll", 
        "BuiltinSecPluginGetInstance", 
        "BuiltinSecPluginFinalize");
    if (retCode != NULL)
    {
        printf("load_security_plugin failed(%d).", retCode);
        return -1;
    }
    // 初始化授权的域参与者
    DomainParticipant* udpDp = DDSIF::CreateDP(150, "encry_example_pub");
    if (NULL == udpDp) { printf("DDSIF::CreateDP failed.\n"); return -2; }
    // 创建数据写者
    DataWriter* rawWriter1 = DDSIF::PubTopic(
        udpDp, "encry_example_1", BytesTypeSupport::get_instance(), "reliable", NULL);
    BytesDataWriter* acWriter1 = (BytesDataWriter*)rawWriter1;
    if (NULL == acWriter1) { printf("DDSIF::PubTopic failed.\n"); return -5; }
    DataWriter* rawWriter2 = DDSIF::PubTopic(
        udpDp, "encry_example_2", BytesTypeSupport::get_instance(), "reliable", NULL);
    BytesDataWriter* acWriter2 = (BytesDataWriter*)rawWriter2;
    if (NULL == acWriter2) { printf("DDSIF::PubTopic failed.\n"); return -5; }
    // 发送数据
    Bytes sample;
    sample.value.ensure_length(1024, 1024);
    unsigned int counter = 0;
    while (++counter < 100)
    {
        // 发送encry_example_1主题数据
        sprintf((char*)sample.value._contiguousBuffer, "sample(%u) from %s.", counter, acWriter1->get_topic()->get_name());
        ReturnCode_t retCode = acWriter1->write(sample, HANDLE_NIL_NATIVE);
        if (retCode != RETCODE_OK)
        {
            printf("send failed(%d).\n", retCode);
        }
        printf("send sample %s\n", sample.value._contiguousBuffer);
        // 发送encry_example_2主题数据
        sprintf((char*)sample.value._contiguousBuffer, "sample(%u) from %s.", counter, acWriter2->get_topic()->get_name());
        retCode = acWriter2->write(sample, HANDLE_NIL_NATIVE);
        if (retCode != RETCODE_OK)
        {
            printf("send failed(%d).\n", retCode);
        }
        printf("send sample %s\n", sample.value._contiguousBuffer);
        ZRSleep(1000);
    }
    return 0;
}

int main(int argc, char* argv[])
{
    return pub(argc, argv);
}
7.4.4. 订阅端代码

注:这里为了减少代码量,使用简化接口,使用标准DCPS接口类似。

#include "ZRDDSCppSimpleInterface.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

using namespace DDS;

// 定义零拷贝数据类型回调接口
class BytesTypeListener :
    virtual public SimpleDataReaderListener<Bytes, BytesSeq, BytesDataReader>
{
public:
    virtual void on_process_sample(DataReader* reader, const Bytes& sample, const SampleInfo& info)
    {
        // 通过该语句获取收到的样本所属主题名称
        const char* topicName = reader->get_topicdescription()->get_name();
        // TODO 在此填写业务处理,接收端sample.userBuffer为用户负载数据,sample.userLength为用户负载长度,无需考虑预留空间
        printf("received data(%s) from topic(%s)\n", 
            sample.value.get_contiguous_buffer(), 
            topicName);
    }
};

int sub()
{
    // 获取入口,并指明QoS配置
    DomainParticipantFactory* factory = DDSIF::Init("./config/zrdds_security_qos.xml", "security_example");
    if (factory == NULL) { printf("DDSIF::Initialize failed.\n"); return -1; }
    // 加载内置插件
    ReturnCode_t retCode = factory->load_security_plugin(
        "BuiltinPlugin",
        "ZRDDSSecBuiltinPlugin.dll",
        "BuiltinSecPluginGetInstance",
        "BuiltinSecPluginFinalize");
    if (retCode != NULL)
    {
        printf("load_security_plugin failed(%d).", retCode);
        return -1;
    }
    // 初始化授权的域参与者
    DomainParticipant* authDP = DDSIF::CreateDP(150, "encry_example_sub");
    if (NULL == authDP) { printf("DDSIF::CreateDP failed.\n"); return -2; }
    // 创建encry_example_1数据读者,并设置监听器,期望成功
    BytesTypeListener* m_listener = new BytesTypeListener();
    DataReader* authDr = DDSIF::SubTopic(
        authDP, "encry_example_1", BytesTypeSupport::get_instance(), "reliable", m_listener);
    if (NULL == authDr) { printf("DDSIF::SubscribeTopic failed.\n"); return -5; }
    // 创建encry_example_2数据读者,并设置监听器,由于权限配置文件中未指明能够订阅该主题,故而订阅失败
    authDr = DDSIF::SubTopic(
        authDP, "encry_example_2", BytesTypeSupport::get_instance(), "reliable", m_listener);
    if (NULL == authDr) { printf("DDSIF::SubscribeTopic failed.\n"); return -5;  }

    getchar();
    DDSIF::Finalize();
    return 0;
}

int main(int argc, char* argv[])
{
    return sub();
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值