zerodds_security/crypto.rs
1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3
4//! Cryptographic-Plugin SPI (OMG DDS-Security 1.1 §8.5).
5//!
6//! Das SPI ist in drei Sub-Interfaces aufgeteilt (Spec-Struktur):
7//! * **KeyFactory** (§8.5.1.7) — Key-Derivation aus `SharedSecret`.
8//! * **KeyExchange** (§8.5.1.8) — Key-Tokens zwischen Peers austauschen.
9//! * **Transform** (§8.5.1.9) — konkrete Encrypt/Decrypt/Sign/Verify.
10//!
11//! Wir bundeln die drei in einem Trait (`CryptographicPlugin`), damit
12//! Nutzer nur einen `Box<dyn ...>` halten muessen. Backend-Impls
13//! (rustls, ring, mbedtls) implementieren alle drei Sub-Interfaces.
14//!
15//! zerodds-lint: allow no_dyn_in_safe
16//! (Plugin-SPI benötigt `Box<dyn CryptographicPlugin>`.)
17
18extern crate alloc;
19
20use alloc::boxed::Box;
21use alloc::vec::Vec;
22
23use crate::authentication::{IdentityHandle, SharedSecretHandle};
24use crate::error::SecurityResult;
25
26/// Opaker Handle fuer ein abgeleitetes Schluesselmaterial (Master-Key
27/// eines Participants/Endpoints).
28#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
29pub struct CryptoHandle(pub u64);
30
31/// Receiver-Specific-MAC (Spec §7.3.6.3 `ReceiverSpecificMAC`).
32///
33/// Wenn ein Sender ein Ciphertext an N Receiver mit **gleicher Suite
34/// aber unterschiedlichen Keys** schickt, wird pro Receiver ein
35/// 16-byte Truncated-HMAC berechnet. Die Wire-Repraesentation ist
36/// eine Sequenz von `(key_id, mac)`-Paaren im SEC_POSTFIX.
37///
38/// `key_id` ist die Spec-konforme 4-Byte-ID (typisch low-32-bits des
39/// Sender-seitigen [`CryptoHandle`] fuer diesen Receiver), anhand
40/// derer der Empfaenger seinen spezifischen MAC-Eintrag findet.
41#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
42pub struct ReceiverMac {
43 /// 4-byte `CryptoTransformKeyId` aus §7.3.6.3.
44 pub key_id: u32,
45 /// 16-byte Truncated-HMAC-SHA256 ueber den Ciphertext.
46 pub mac: [u8; 16],
47}
48
49impl ReceiverMac {
50 /// Wire-Size eines einzelnen `ReceiverMac`-Eintrags (Spec §7.3.6.3).
51 pub const WIRE_SIZE: usize = 4 + 16;
52}
53
54/// Cryptographic-Plugin (Spec §8.5.1). In v1.3 ist das ein reines
55/// **Interface** — Produktions-Impls leben in `zerodds-security-crypto`
56/// (AES-GCM + HMAC), `zerodds-security-keyexchange` (DH-Keyexchange,
57/// Spec §9.5.3) und `zerodds-security-rtps` (RTPS-Header-AAD-Wrapper,
58/// Spec §7.3.5).
59pub trait CryptographicPlugin: Send + Sync {
60 // -------- KeyFactory (§8.5.1.7) --------
61
62 /// Erzeugt Participant-Crypto-Material aus dem Handshake-
63 /// SharedSecret.
64 fn register_local_participant(
65 &mut self,
66 identity: IdentityHandle,
67 properties: &[(&str, &str)],
68 ) -> SecurityResult<CryptoHandle>;
69
70 /// Erzeugt Crypto-Material fuer einen Remote-Participant.
71 fn register_matched_remote_participant(
72 &mut self,
73 local: CryptoHandle,
74 remote_identity: IdentityHandle,
75 shared_secret: SharedSecretHandle,
76 ) -> SecurityResult<CryptoHandle>;
77
78 /// Erzeugt Crypto-Material fuer einen lokalen DataWriter/Reader.
79 fn register_local_endpoint(
80 &mut self,
81 participant: CryptoHandle,
82 is_writer: bool,
83 properties: &[(&str, &str)],
84 ) -> SecurityResult<CryptoHandle>;
85
86 // -------- KeyExchange (§8.5.1.8) --------
87
88 /// Erzeugt das `ParticipantCryptoTokens`-Blob, das an den Remote-
89 /// Participant gesendet wird (enthaelt verschluesseltes
90 /// Key-Material).
91 fn create_local_participant_crypto_tokens(
92 &mut self,
93 local: CryptoHandle,
94 remote: CryptoHandle,
95 ) -> SecurityResult<Vec<u8>>;
96
97 /// Verarbeitet die Tokens vom Remote-Participant. Danach sind die
98 /// Keys fuer encrypted Submessages wechselseitig bekannt.
99 fn set_remote_participant_crypto_tokens(
100 &mut self,
101 local: CryptoHandle,
102 remote: CryptoHandle,
103 tokens: &[u8],
104 ) -> SecurityResult<()>;
105
106 // -------- Transform (§8.5.1.9) --------
107
108 /// Encrypt + Sign einer RTPS-Submessage. Input: plain submessage
109 /// bytes. Output: `SecureSubmessage`-Payload (ciphertext + tag).
110 ///
111 /// `aad_extension` ist die Spec-konforme AAD-Extension (Spec §10.5.2
112 /// Tab.78). Submessage-Protection (§8.5.1.9.2) liefert hier
113 /// `SubmessageHeader || SecureSubmessageHeader`-Bytes;
114 /// RTPS-Message-Protection (§8.5.1.9.7) den RTPS-Header (20 Byte) +
115 /// SecureRTPSSubmessageHeader. Leer (`&[]`) ist nur spec-konform
116 /// wenn der Caller explizit Spec-§8.1 Tab.78 ohne Header-Coverage
117 /// akzeptiert (z.B. Pre-Shared-Key-Pfad ohne Header-Auth).
118 ///
119 /// Spec §8.5.1.9.1 `encode_serialized_payload`.
120 fn encrypt_submessage(
121 &self,
122 local: CryptoHandle,
123 remote_list: &[CryptoHandle],
124 plaintext: &[u8],
125 aad_extension: &[u8],
126 ) -> SecurityResult<Vec<u8>>;
127
128 /// Decrypt + Verify. Output: plain submessage bytes. `aad_extension`
129 /// muss byte-identisch zur Sender-AAD sein (sonst Tag-Mismatch).
130 ///
131 /// Spec §8.5.1.9.4 `decode_serialized_payload`.
132 fn decrypt_submessage(
133 &self,
134 local: CryptoHandle,
135 remote: CryptoHandle,
136 ciphertext: &[u8],
137 aad_extension: &[u8],
138 ) -> SecurityResult<Vec<u8>>;
139
140 /// Encrypt+Sign mit `Receiver-Specific-MACs` (Spec
141 /// §7.3.6.3). Produziert **einen** Ciphertext (Sender-Key) plus
142 /// pro Remote einen 16-byte Truncated-HMAC.
143 ///
144 /// Die `receivers`-Liste enthaelt pro Empfaenger `(handle,
145 /// key_id)`:
146 /// * `handle` — CryptoHandle auf den MAC-Key im Plugin-Slot
147 /// (typisch der aus `register_matched_remote_participant`
148 /// abgeleitete Per-Peer-Key).
149 /// * `key_id` — 4-byte Wire-Identifier, den der Empfaenger in
150 /// der MAC-Liste sucht (muss zwischen Sender und Empfaenger
151 /// synchronisiert sein, typisch low-32-bits des Peer-GuidPrefix).
152 ///
153 /// Default-Impl: faellt auf `encrypt_submessage` zurueck und
154 /// liefert leere MAC-Liste — Plugins ohne Multi-MAC-Support
155 /// signalisieren dem Caller damit "bitte Multi-Cipher-Fan-Out
156 /// nutzen".
157 fn encrypt_submessage_multi(
158 &self,
159 local: CryptoHandle,
160 receivers: &[(CryptoHandle, u32)],
161 plaintext: &[u8],
162 aad_extension: &[u8],
163 ) -> SecurityResult<(Vec<u8>, Vec<ReceiverMac>)> {
164 let handles: Vec<CryptoHandle> = receivers.iter().map(|(h, _)| *h).collect();
165 let ciphertext = self.encrypt_submessage(local, &handles, plaintext, aad_extension)?;
166 Ok((ciphertext, Vec::new()))
167 }
168
169 /// Verify Receiver-Specific-MAC + Decrypt.
170 ///
171 /// `own_key_id` ist die Wire-Id, unter der der Empfaenger in der
172 /// MAC-Liste zu finden ist; `own_mac_key_handle` ist der Slot mit
173 /// dem dazugehoerigen HMAC-Key.
174 ///
175 /// Wenn `macs.is_empty()` wird auf [`Self::decrypt_submessage`]
176 /// delegiert (Backward-Compat).
177 ///
178 /// # Errors
179 /// * `CryptoFailed` wenn kein MAC-Eintrag zur `own_key_id`
180 /// passt oder der MAC-Vergleich fehlschlaegt.
181 #[allow(clippy::too_many_arguments)]
182 fn decrypt_submessage_with_receiver_mac(
183 &self,
184 local: CryptoHandle,
185 remote: CryptoHandle,
186 own_key_id: u32,
187 own_mac_key_handle: CryptoHandle,
188 ciphertext: &[u8],
189 macs: &[ReceiverMac],
190 aad_extension: &[u8],
191 ) -> SecurityResult<Vec<u8>> {
192 let _ = (own_key_id, own_mac_key_handle);
193 if macs.is_empty() {
194 return self.decrypt_submessage(local, remote, ciphertext, aad_extension);
195 }
196 Err(crate::error::SecurityError::new(
197 crate::error::SecurityErrorKind::NotImplemented,
198 "plugin does not implement receiver-specific mac verification",
199 ))
200 }
201
202 /// Plugin-Class-Id (z.B. "DDS:Crypto:AES-GCM-GMAC:1.2").
203 fn plugin_class_id(&self) -> &str;
204}
205
206/// Factory-Alias.
207pub type CryptoPluginBox = Box<dyn CryptographicPlugin>;