Skip to main content

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>;