Skip to main content

zerodds_security/
authentication.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3
4//! Authentication-Plugin SPI (OMG DDS-Security 1.1 §8.3).
5//!
6//! Verantwortlich fuer:
7//! 1. **Identity-Validation** beim Participant-Start — lokale Identity
8//!    (z.B. X.509-Cert + Private-Key) gegen Trust-Anchor pruefen.
9//! 2. **Identity-Handshake** zwischen zwei Participants — Challenge/
10//!    Response mit signierten Noncen, Spec §8.3.2.
11//! 3. **SharedSecret-Erzeugung** am Ende des Handshakes — Eingabe
12//!    fuers CryptographicPlugin fuer Key-Derivation.
13//!
14//! Der SPI ist **State-Machine-Light** — das Plugin haelt den
15//! Handshake-State selbst. Der Caller (DCPS-Layer) triggert nur
16//! `begin_handshake_request/reply`, `process_handshake_reply`,
17//! `process_handshake_final`.
18//!
19//! zerodds-lint: allow no_dyn_in_safe
20//! (Plugin-SPI benötigt `Box<dyn AuthenticationPlugin>`.)
21
22extern crate alloc;
23
24use alloc::boxed::Box;
25use alloc::vec::Vec;
26
27use crate::error::SecurityResult;
28use crate::properties::PropertyList;
29
30/// Opaker Handle fuer eine validierte Identity. Der Plugin-Interne
31/// Zustand (X.509-Cert, Keys) wird nicht ueber diesen Handle nach
32/// aussen exponiert.
33#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
34pub struct IdentityHandle(pub u64);
35
36/// Opaker Handle fuer einen laufenden Handshake.
37#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
38pub struct HandshakeHandle(pub u64);
39
40/// Opaker Handle fuer ein geteiltes Geheimnis (Ausgabe eines
41/// abgeschlossenen Handshakes). Wird an `CryptographicPlugin`
42/// weitergereicht.
43#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
44pub struct SharedSecretHandle(pub u64);
45
46/// Lookup-Bruecke zwischen [`AuthenticationPlugin`] und
47/// [`crate::crypto::CryptographicPlugin`].
48///
49/// Der Authentication-Plugin produziert nach Handshake einen
50/// [`SharedSecretHandle`] und kennt die zugehoerigen 32 Byte aus
51/// `x25519 + HKDF-SHA256`. Der Crypto-Plugin braucht genau diese
52/// Bytes um seinen per-peer Master-Key abzuleiten — statt einen
53/// random Key zu generieren und als opaken Token durch die
54/// Governance zu routen.
55///
56/// Implementierer muessen **Send + Sync** sein, damit der Crypto-
57/// Plugin den Provider via `Arc<dyn SharedSecretProvider>` halten
58/// kann.
59pub trait SharedSecretProvider: Send + Sync {
60    /// Liefert die rohen Bytes des geteilten Schluessels.
61    /// `None` wenn der Handle unbekannt ist (Handshake noch nicht
62    /// abgeschlossen oder bereits verworfen).
63    fn get_shared_secret(&self, handle: SharedSecretHandle) -> Option<alloc::vec::Vec<u8>>;
64}
65
66/// Ergebnis eines Handshake-Steps.
67#[derive(Debug, Clone)]
68#[non_exhaustive]
69pub enum HandshakeStepOutcome {
70    /// Plugin will eine weitere Nachricht an den Peer senden.
71    SendMessage {
72        /// Opaker Handshake-Token als Byte-Blob. Geht 1:1 in die
73        /// `AuthHandshake`-Builtin-Submessage (Spec §9.3.2.3).
74        token: Vec<u8>,
75    },
76    /// Handshake erfolgreich abgeschlossen — `SharedSecretHandle`
77    /// verwendbar.
78    Complete {
79        /// Geteilter Schluessel-Handle fuer den CryptographicPlugin.
80        secret: SharedSecretHandle,
81    },
82    /// Handshake brauch noch Nachricht vom Peer — nichts zu tun.
83    WaitingForPeer,
84}
85
86/// Authentication-Plugin-Trait. Spec §8.3.2.7.
87pub trait AuthenticationPlugin: Send + Sync {
88    /// Wird einmal beim Participant-Start aufgerufen: lokale Identity
89    /// validieren (Zertifikat, Key, Trust-Anchor) und einen Handle
90    /// zurueckgeben.
91    ///
92    /// # Spec
93    /// §8.3.2.7.1 `validate_local_identity`.
94    fn validate_local_identity(
95        &mut self,
96        props: &PropertyList,
97        participant_guid: [u8; 16],
98    ) -> SecurityResult<IdentityHandle>;
99
100    /// Wird aufgerufen, sobald via SPDP ein Remote-Participant entdeckt
101    /// wurde. Plugin validiert das Remote-Cert (aus `remote_auth_token`)
102    /// gegen seinen Trust-Store.
103    ///
104    /// # Spec
105    /// §8.3.2.7.2 `validate_remote_identity`.
106    fn validate_remote_identity(
107        &mut self,
108        local: IdentityHandle,
109        remote_participant_guid: [u8; 16],
110        remote_auth_token: &[u8],
111    ) -> SecurityResult<IdentityHandle>;
112
113    /// Startet den Handshake. Liefert das erste Token, das an den
114    /// Peer gesendet werden muss.
115    ///
116    /// # Spec
117    /// §8.3.2.7.3 `begin_handshake_request`.
118    fn begin_handshake_request(
119        &mut self,
120        initiator: IdentityHandle,
121        replier: IdentityHandle,
122    ) -> SecurityResult<(HandshakeHandle, HandshakeStepOutcome)>;
123
124    /// Peer-Seite des Handshake-Starts. `request_token` ist was der
125    /// Initiator per `begin_handshake_request` geschickt hat.
126    ///
127    /// # Spec
128    /// §8.3.2.7.4 `begin_handshake_reply`.
129    fn begin_handshake_reply(
130        &mut self,
131        replier: IdentityHandle,
132        initiator: IdentityHandle,
133        request_token: &[u8],
134    ) -> SecurityResult<(HandshakeHandle, HandshakeStepOutcome)>;
135
136    /// Folge-Nachrichten des Handshakes durchreichen.
137    ///
138    /// # Spec
139    /// §8.3.2.7.5 `process_handshake`.
140    fn process_handshake(
141        &mut self,
142        handshake: HandshakeHandle,
143        token: &[u8],
144    ) -> SecurityResult<HandshakeStepOutcome>;
145
146    /// Beendet den Handshake und liefert den finalen SharedSecret.
147    /// Fehlschlag bricht ab. Wird nach `Complete`-Outcome durch den
148    /// Caller aufgerufen, um den Secret aus dem Plugin zu ziehen.
149    ///
150    /// Alternative: der `Complete`-Outcome enthaelt den Handle
151    /// bereits — diese Methode ist nur fuer Polling-Integrations.
152    ///
153    /// # Spec
154    /// §8.3.2.7.8 `get_shared_secret`.
155    fn shared_secret(&self, handshake: HandshakeHandle) -> SecurityResult<SharedSecretHandle>;
156
157    /// Identity-Plugin-Name (z.B. "DDS:Auth:PKI-DH:1.2"). Wird in SPDP als
158    /// `dds.sec.auth.plugin_class` annonciert.
159    fn plugin_class_id(&self) -> &str;
160
161    /// Liefert das `IdentityToken` fuer eine Local-Identity (Spec
162    /// §9.3.2.4). Wird im SPDP-Announce als `PID_IDENTITY_TOKEN` (0x1001)
163    /// publiziert. Default: leerer Token (= Plugin unterstuetzt das
164    /// Feature nicht).
165    ///
166    /// # Errors
167    /// Implementations-spezifisch.
168    fn get_identity_token(&self, _local: IdentityHandle) -> SecurityResult<Vec<u8>> {
169        Ok(Vec::new())
170    }
171
172    /// Liefert das `IdentityStatusToken` fuer eine Local-Identity
173    /// (Spec §9.3.2.5.1.2). Default: leer.
174    ///
175    /// # Errors
176    /// Implementations-spezifisch.
177    fn get_identity_status_token(&self, _local: IdentityHandle) -> SecurityResult<Vec<u8>> {
178        Ok(Vec::new())
179    }
180
181    /// Setzt das Permissions-Credential und das Permissions-Token an
182    /// einer Local-Identity (Spec §9.3.2.4 + §9.3.2.5.4). Wird vom
183    /// Caller-Layer mit dem Output des AccessControlPlugin gespeist.
184    ///
185    /// # Errors
186    /// Default: `Unsupported` (Plugin ignoriert Permissions-Bind).
187    fn set_permissions_credential_and_token(
188        &mut self,
189        _local: IdentityHandle,
190        _permissions_credential: &[u8],
191        _permissions_token: &[u8],
192    ) -> SecurityResult<()> {
193        Ok(())
194    }
195
196    /// Liefert das `AuthenticatedPeerCredentialToken` (Spec §9.3.2.5.6).
197    /// Wird nach erfolgreichem Handshake vom AccessControl-Layer
198    /// abgeholt, um den Caller-Subject-Match durchzufuehren.
199    /// Default: leer.
200    ///
201    /// # Errors
202    /// Implementations-spezifisch.
203    fn get_authenticated_peer_credential_token(
204        &self,
205        _handshake: HandshakeHandle,
206    ) -> SecurityResult<Vec<u8>> {
207        Ok(Vec::new())
208    }
209}
210
211/// Factory-Alias — vermeidet `Box<dyn ...>`-Boilerplate an Call-Sites.
212pub type AuthPluginBox = Box<dyn AuthenticationPlugin>;
213
214#[cfg(test)]
215#[allow(clippy::expect_used)]
216mod tests {
217    use super::*;
218
219    // Compile-Check: Stub-Impl demonstriert dass der Trait object-safe
220    // + Send+Sync erfuellbar ist.
221    struct StubAuth;
222    impl AuthenticationPlugin for StubAuth {
223        fn validate_local_identity(
224            &mut self,
225            _props: &PropertyList,
226            _guid: [u8; 16],
227        ) -> SecurityResult<IdentityHandle> {
228            Ok(IdentityHandle(1))
229        }
230        fn validate_remote_identity(
231            &mut self,
232            _l: IdentityHandle,
233            _r: [u8; 16],
234            _t: &[u8],
235        ) -> SecurityResult<IdentityHandle> {
236            Ok(IdentityHandle(2))
237        }
238        fn begin_handshake_request(
239            &mut self,
240            _i: IdentityHandle,
241            _r: IdentityHandle,
242        ) -> SecurityResult<(HandshakeHandle, HandshakeStepOutcome)> {
243            Ok((HandshakeHandle(1), HandshakeStepOutcome::WaitingForPeer))
244        }
245        fn begin_handshake_reply(
246            &mut self,
247            _r: IdentityHandle,
248            _i: IdentityHandle,
249            _t: &[u8],
250        ) -> SecurityResult<(HandshakeHandle, HandshakeStepOutcome)> {
251            Ok((HandshakeHandle(1), HandshakeStepOutcome::WaitingForPeer))
252        }
253        fn process_handshake(
254            &mut self,
255            _h: HandshakeHandle,
256            _t: &[u8],
257        ) -> SecurityResult<HandshakeStepOutcome> {
258            Ok(HandshakeStepOutcome::Complete {
259                secret: SharedSecretHandle(1),
260            })
261        }
262        fn shared_secret(&self, _h: HandshakeHandle) -> SecurityResult<SharedSecretHandle> {
263            Ok(SharedSecretHandle(1))
264        }
265        fn plugin_class_id(&self) -> &str {
266            "DDS:Auth:Stub"
267        }
268    }
269
270    #[test]
271    fn stub_can_be_boxed() {
272        let _: AuthPluginBox = Box::new(StubAuth);
273    }
274}