1 · PKI trust anchor with a self-signed root CA
When: closed-environment setups without an external CA (lab, small edge deployments). A self-signed root cert serves as the trust anchor; all participant certs hang below it.
# 1) create the root CA (once, keep it safe)
openssl req -x509 -newkey rsa:4096 -keyout ca-key.pem -out ca-cert.pem \
-sha256 -days 3650 -nodes \
-subj "/CN=ZeroDDS Root CA/O=Acme/C=DE"
# 2) issue a participant cert (per host/service)
openssl req -newkey rsa:2048 -keyout part-key.pem -out part.csr \
-nodes -subj "/CN=robot-arm-7/O=Acme/C=DE"
openssl x509 -req -in part.csr -CA ca-cert.pem -CAkey ca-key.pem \
-CAcreateserial -out part-cert.pem -days 365 -sha256
ZeroDDS side (PropertyQosPolicy on the participant):
let participant_qos = DomainParticipantQos {
property: PropertyQosPolicy::new()
.with("dds.sec.auth.identity_ca", "file:/etc/zerodds/ca-cert.pem")
.with("dds.sec.auth.identity_certificate", "file:/etc/zerodds/part-cert.pem")
.with("dds.sec.auth.private_key", "file:/etc/zerodds/part-key.pem"),
..Default::default()
};
Deeper material: PKI Governance & cert lifecycle for CRL, cert rotation, HSM integration.
2 · Permissions XML for multi-topic setups
When: multiple topics, multiple identities, a clear read/write split (e.g. Telemetry: only the robot may publish, everyone may read; Setpoint: only the operator may publish).
<permissions>
<grant name="robot_grant">
<subject_name>CN=robot-arm-7,O=Acme,C=DE</subject_name>
<validity>
<not_before>2026-01-01T00:00:00</not_before>
<not_after>2027-01-01T00:00:00</not_after>
</validity>
<allow_rule>
<domains><id>0</id></domains>
<publish><topics><topic>Telemetry</topic></topics></publish>
<subscribe><topics><topic>Setpoint</topic></topics></subscribe>
</allow_rule>
<default>DENY</default>
</grant>
</permissions>
CMS signature (mandatory):
# sign the permissions XML with the CA
openssl cms -sign -in permissions.xml -text \
-out permissions.p7s -outform PEM \
-signer ca-cert.pem -inkey ca-key.pem -nocerts
ZeroDDS side: property dds.sec.access.permissions = file:/etc/zerodds/permissions.p7s, plus dds.sec.access.governance for domain-level policies.
Pattern match: <topic>customer-*/odom</topic> allows globs — see the AccessControl plugin reference.
3 · AES-GCM vs. AES-GMAC — when which
AES-GCM: confidentiality + integrity. The sample payload is encrypted. Highest protection level, but highest CPU cost.
AES-GMAC: integrity only (MAC). The sample payload stays cleartext. Protects against tampering, but not against eavesdroppers. ~3× faster than GCM.
Config (governance XML, topic level):
<topic_rule>
<topic_expression>Telemetry</topic_expression>
<enable_discovery_protection>true</enable_discovery_protection>
<enable_liveliness_protection>true</enable_liveliness_protection>
<enable_read_access_control>true</enable_read_access_control>
<enable_write_access_control>true</enable_write_access_control>
<metadata_protection_kind>SIGN</metadata_protection_kind> <!-- GMAC -->
<data_protection_kind>ENCRYPT</data_protection_kind> <!-- GCM -->
</topic_rule>
Trade-off: ENCRYPT on high-frequency topics can make the hot path a bottleneck. Profile with otel tracing before production.
4 · AES-NI hardware acceleration
When: CPU-bound with AES-GCM. AES-NI has been mainstream since Intel Westmere (2010) + AMD Bulldozer (2011) — it's on every current server.
ZeroDDS build:
# default: AES-NI is auto-detected (cpuid)
cargo build -p zerodds-dcps --features security --release
# force explicitly (e.g. cross-compile):
RUSTFLAGS="-C target-feature=+aes,+pclmulqdq" cargo build ...
Verification: zerodds-security-crypto logs the chosen backend variant (software vs. AES-NI) at init. 5-10× speed-up for AES-GCM 256.
ARM: ARMv8 crypto extensions analogously; +aes,+pmull as the target feature.
Code path: crates/security-crypto/src/aes_gcm_hw.rs
5 · Multi-sink audit logging
When: compliance obligation for an audit trail (CRA, ISO 27001). You need stderr for interactive inspection + JSONL for the log aggregator + syslog for SIEM.
use zerodds_dcps::qos::DomainParticipantQos;
use zerodds_qos::PropertyQosPolicy;
// DDS-Security spec-style logger wireup: the participant carries dds.sec.log.*
// properties; the runtime builds a fan-out logger from them on create.
let qos = DomainParticipantQos {
property: PropertyQosPolicy::new()
.with("dds.sec.log.plugin", "stderr,jsonl") // fan out to both
.with("dds.sec.log.level", "Notice") // min level
.with("dds.sec.log.jsonl.path", "/var/log/zerodds-audit.jsonl"),
..Default::default()
};
▶ Runnable example: rust-security-plugin-chain
JSONL format: one event per line, all DDS-Security events follow §9.6.2.1.1: { "ts": "...", "severity": "...", "plugin_class": "AccessControl", "msg": "...", "subject": "...", "topic": "..." }.
SIEM integration: syslog facility LOG_AUTH; a SIEM rule on plugin_class=AccessControl AND severity≥Warning for permission violations.
6 · Pen-test smoke against the plugin chain
When: before every production release. A smoke test of whether the chain really blocks what it should block.
Test matrix:
- Authentication: a participant without a cert tries to join → must be rejected (
SECURITY_AUTHENTICATION_FAILED log event).
- AccessControl: identity X (no permissions grant for topic
Setpoint) tries to publish → DataWriter creation must throw NotAllowedByAccessControl.
- Cryptographic: sniff along with Wireshark + the zerodds DDS dissector — the sample payload must be encrypted (no cleartext IDL).
- Logging: every block event must be in the audit trail at severity ≥ Warning.
- DataTagging: a reader with a
level=PUBLIC filter must not see a level=SECRET sample.
Tools: zerodds-conformance has a security conformance test suite; zerodds-chaos can inject crypto tamper faults.