1use alloc::collections::{BTreeMap, BTreeSet};
41use alloc::string::{String, ToString};
42use alloc::vec::Vec;
43use core::sync::atomic::{AtomicU64, Ordering};
44
45use ring::hkdf;
46use ring::hmac;
47use ring::rand::{SecureRandom, SystemRandom};
48use zerodds_security::authentication::{
49 AuthenticationPlugin, HandshakeHandle, HandshakeStepOutcome, IdentityHandle,
50 SharedSecretHandle, SharedSecretProvider,
51};
52use zerodds_security::error::{SecurityError, SecurityErrorKind, SecurityResult};
53use zerodds_security::properties::PropertyList;
54use zerodds_security::token::DataHolder;
55
56pub mod class_id {
58 pub const PSK: &str = "DDS:Auth:PSK:1.2";
60 pub const REQUEST: &str = "DDS:Auth:PSK:1.2+AuthReq";
62 pub const REPLY: &str = "DDS:Auth:PSK:1.2+AuthReply";
64 pub const FINAL: &str = "DDS:Auth:PSK:1.2+AuthFinal";
66}
67
68pub mod prop {
70 pub const PSK_ID: &str = "psk.id";
72 pub const CHALLENGE1: &str = "challenge1";
74 pub const CHALLENGE2: &str = "challenge2";
76 pub const HMAC: &str = "hmac";
78 pub const KAGREE_ALGO: &str = "c.kagree_algo";
80}
81
82pub const PROP_PSK_ID: &str = "dds.psk.identity_id";
84pub const PROP_PSK_KEY_HEX: &str = "dds.psk.pre_shared_key_hex";
86
87const REPLAY_CACHE_CAP: usize = 1024;
89
90pub const HKDF_INFO_SHARED_SECRET: &[u8] = b"DDS-Security-1.2-PSK";
93
94pub struct PskAuthenticationPlugin {
96 next_handle: AtomicU64,
97 psks: BTreeMap<String, Vec<u8>>,
99 identities: BTreeMap<IdentityHandle, String>,
101 pending_initiator: BTreeMap<HandshakeHandle, InitiatorState>,
103 pending_replier: BTreeMap<HandshakeHandle, ReplierState>,
105 handshake_to_secret: BTreeMap<HandshakeHandle, SharedSecretHandle>,
107 secrets: BTreeMap<SharedSecretHandle, Vec<u8>>,
109 replay_cache: BTreeMap<IdentityHandle, BTreeSet<[u8; 32]>>,
112 replay_order: BTreeMap<IdentityHandle, Vec<[u8; 32]>>,
114}
115
116struct InitiatorState {
117 local: IdentityHandle,
118 psk_id: String,
119 challenge1: [u8; 32],
120}
121
122struct ReplierState {
123 local: IdentityHandle,
124 psk_id: String,
125 challenge1: [u8; 32],
126 challenge2: [u8; 32],
127 secret_handle: SharedSecretHandle,
128}
129
130impl Default for PskAuthenticationPlugin {
131 fn default() -> Self {
132 Self::new()
133 }
134}
135
136impl PskAuthenticationPlugin {
137 #[must_use]
139 pub fn new() -> Self {
140 Self {
141 next_handle: AtomicU64::new(0),
142 psks: BTreeMap::new(),
143 identities: BTreeMap::new(),
144 pending_initiator: BTreeMap::new(),
145 pending_replier: BTreeMap::new(),
146 handshake_to_secret: BTreeMap::new(),
147 secrets: BTreeMap::new(),
148 replay_cache: BTreeMap::new(),
149 replay_order: BTreeMap::new(),
150 }
151 }
152
153 fn next_id(&self) -> u64 {
154 self.next_handle.fetch_add(1, Ordering::Relaxed) + 1
155 }
156
157 pub fn register_psk(&mut self, id: String, key: Vec<u8>) -> SecurityResult<()> {
163 if id.is_empty() {
164 return Err(SecurityError::new(
165 SecurityErrorKind::BadArgument,
166 "psk: identity-id leer",
167 ));
168 }
169 if key.is_empty() {
170 return Err(SecurityError::new(
171 SecurityErrorKind::BadArgument,
172 "psk: pre-shared-key leer",
173 ));
174 }
175 self.psks.insert(id, key);
176 Ok(())
177 }
178
179 pub fn validate_local_psk_identity(
184 &mut self,
185 identity_id: &str,
186 ) -> SecurityResult<IdentityHandle> {
187 if !self.psks.contains_key(identity_id) {
188 return Err(SecurityError::new(
189 SecurityErrorKind::BadArgument,
190 alloc::format!("psk: unbekannte identity-id '{identity_id}'"),
191 ));
192 }
193 let handle = IdentityHandle(self.next_id());
194 self.identities.insert(handle, identity_id.to_string());
195 Ok(handle)
196 }
197
198 pub fn validate_remote_psk_identity(
206 &mut self,
207 remote_token: &[u8],
208 ) -> SecurityResult<IdentityHandle> {
209 let dh = DataHolder::from_cdr_le(remote_token)?;
210 if dh.class_id != class_id::PSK {
211 return Err(SecurityError::new(
212 SecurityErrorKind::AuthenticationFailed,
213 alloc::format!(
214 "psk: remote-IdentityToken hat falsche class_id '{}'",
215 dh.class_id
216 ),
217 ));
218 }
219 let id = dh.property(PROP_PSK_ID).ok_or_else(|| {
220 SecurityError::new(
221 SecurityErrorKind::AuthenticationFailed,
222 "psk: IdentityToken ohne psk.id",
223 )
224 })?;
225 if !self.psks.contains_key(id) {
226 return Err(SecurityError::new(
227 SecurityErrorKind::AuthenticationFailed,
228 alloc::format!("psk: remote psk.id '{id}' nicht im lokalen Trust-Store"),
229 ));
230 }
231 let handle = IdentityHandle(self.next_id());
232 self.identities.insert(handle, id.to_string());
233 Ok(handle)
234 }
235
236 pub fn build_identity_token(&self, local: IdentityHandle) -> SecurityResult<Vec<u8>> {
242 let id = self.identities.get(&local).ok_or_else(|| {
243 SecurityError::new(SecurityErrorKind::BadArgument, "psk: unbekannter Handle")
244 })?;
245 let dh = DataHolder::new(class_id::PSK).with_property(PROP_PSK_ID, id.clone());
246 Ok(dh.to_cdr_le())
247 }
248
249 #[must_use]
251 pub fn secret_bytes(&self, handle: SharedSecretHandle) -> Option<&[u8]> {
252 self.secrets.get(&handle).map(Vec::as_slice)
253 }
254
255 fn store_secret(&mut self, bytes: Vec<u8>) -> SharedSecretHandle {
256 let handle = SharedSecretHandle(self.next_id());
257 self.secrets.insert(handle, bytes);
258 handle
259 }
260
261 fn record_challenge(&mut self, local: IdentityHandle, c: [u8; 32]) -> SecurityResult<()> {
262 let cache = self.replay_cache.entry(local).or_default();
263 if cache.contains(&c) {
264 return Err(SecurityError::new(
265 SecurityErrorKind::AuthenticationFailed,
266 "psk: replayed challenge1 detected",
267 ));
268 }
269 cache.insert(c);
270 let order = self.replay_order.entry(local).or_default();
271 order.push(c);
272 if order.len() > REPLAY_CACHE_CAP {
273 let dropped = order.remove(0);
274 cache.remove(&dropped);
275 }
276 Ok(())
277 }
278
279 fn lookup_psk(&self, id: &str) -> SecurityResult<&[u8]> {
280 self.psks.get(id).map(Vec::as_slice).ok_or_else(|| {
281 SecurityError::new(
282 SecurityErrorKind::AuthenticationFailed,
283 alloc::format!("psk: unbekannte identity-id '{id}'"),
284 )
285 })
286 }
287}
288
289impl SharedSecretProvider for PskAuthenticationPlugin {
290 fn get_shared_secret(&self, handle: SharedSecretHandle) -> Option<Vec<u8>> {
291 self.secrets.get(&handle).cloned()
292 }
293}
294
295fn random_challenge() -> SecurityResult<[u8; 32]> {
297 let rng = SystemRandom::new();
298 let mut buf = [0u8; 32];
299 rng.fill(&mut buf).map_err(|_| {
300 SecurityError::new(
301 SecurityErrorKind::CryptoFailed,
302 "psk: SystemRandom not available",
303 )
304 })?;
305 Ok(buf)
306}
307
308fn hmac_input(psk_id: &str, ch1: &[u8; 32], ch2: &[u8; 32]) -> Vec<u8> {
311 let mut out = Vec::with_capacity(4 + psk_id.len() + 4 + 32 + 4 + 32);
312 out.extend_from_slice(&(psk_id.len() as u32).to_le_bytes());
313 out.extend_from_slice(psk_id.as_bytes());
314 out.extend_from_slice(&(32u32).to_le_bytes());
315 out.extend_from_slice(ch1);
316 out.extend_from_slice(&(32u32).to_le_bytes());
317 out.extend_from_slice(ch2);
318 out
319}
320
321fn hmac_sign(psk: &[u8], psk_id: &str, ch1: &[u8; 32], ch2: &[u8; 32]) -> [u8; 32] {
322 let key = hmac::Key::new(hmac::HMAC_SHA256, psk);
323 let tag = hmac::sign(&key, &hmac_input(psk_id, ch1, ch2));
324 let mut out = [0u8; 32];
325 out.copy_from_slice(tag.as_ref());
326 out
327}
328
329fn hmac_verify(
330 psk: &[u8],
331 psk_id: &str,
332 ch1: &[u8; 32],
333 ch2: &[u8; 32],
334 tag: &[u8],
335) -> SecurityResult<()> {
336 let key = hmac::Key::new(hmac::HMAC_SHA256, psk);
337 hmac::verify(&key, &hmac_input(psk_id, ch1, ch2), tag).map_err(|_| {
338 SecurityError::new(
339 SecurityErrorKind::AuthenticationFailed,
340 "psk: hmac verify failed",
341 )
342 })
343}
344
345pub fn derive_psk_shared_secret(
348 psk: &[u8],
349 ch1: &[u8; 32],
350 ch2: &[u8; 32],
351) -> SecurityResult<[u8; 32]> {
352 let mut salt = [0u8; 64];
353 salt[..32].copy_from_slice(ch1);
354 salt[32..].copy_from_slice(ch2);
355 let salt_obj = hkdf::Salt::new(hkdf::HKDF_SHA256, &salt);
356 let prk = salt_obj.extract(psk);
357 let info = [HKDF_INFO_SHARED_SECRET];
358 let okm = prk.expand(&info, hkdf::HKDF_SHA256).map_err(|_| {
359 SecurityError::new(SecurityErrorKind::CryptoFailed, "psk: HKDF expand failed")
360 })?;
361 let mut out = [0u8; 32];
362 okm.fill(&mut out).map_err(|_| {
363 SecurityError::new(SecurityErrorKind::CryptoFailed, "psk: HKDF fill failed")
364 })?;
365 Ok(out)
366}
367
368fn read_32(dh: &DataHolder, name: &str) -> SecurityResult<[u8; 32]> {
369 let bytes = dh.binary_property(name).ok_or_else(|| {
370 SecurityError::new(
371 SecurityErrorKind::AuthenticationFailed,
372 alloc::format!("psk: missing binary property '{name}'"),
373 )
374 })?;
375 if bytes.len() != 32 {
376 return Err(SecurityError::new(
377 SecurityErrorKind::AuthenticationFailed,
378 alloc::format!("psk: '{name}' must be 32 bytes"),
379 ));
380 }
381 let mut out = [0u8; 32];
382 out.copy_from_slice(bytes);
383 Ok(out)
384}
385
386impl AuthenticationPlugin for PskAuthenticationPlugin {
387 fn validate_local_identity(
388 &mut self,
389 props: &PropertyList,
390 _participant_guid: [u8; 16],
391 ) -> SecurityResult<IdentityHandle> {
392 let id = props.get(PROP_PSK_ID).ok_or_else(|| {
393 SecurityError::new(
394 SecurityErrorKind::InvalidConfiguration,
395 "psk: fehlt dds.psk.identity_id",
396 )
397 })?;
398 if let Some(hex) = props.get(PROP_PSK_KEY_HEX) {
400 let bytes = hex_decode(hex)?;
401 self.register_psk(id.to_string(), bytes)?;
402 }
403 self.validate_local_psk_identity(id)
404 }
405
406 fn validate_remote_identity(
407 &mut self,
408 _local: IdentityHandle,
409 _remote_participant_guid: [u8; 16],
410 remote_auth_token: &[u8],
411 ) -> SecurityResult<IdentityHandle> {
412 self.validate_remote_psk_identity(remote_auth_token)
413 }
414
415 fn begin_handshake_request(
416 &mut self,
417 initiator: IdentityHandle,
418 _replier: IdentityHandle,
419 ) -> SecurityResult<(HandshakeHandle, HandshakeStepOutcome)> {
420 let psk_id = self
421 .identities
422 .get(&initiator)
423 .ok_or_else(|| {
424 SecurityError::new(
425 SecurityErrorKind::BadArgument,
426 "psk: unbekannter Initiator-IdentityHandle",
427 )
428 })?
429 .clone();
430 let challenge1 = random_challenge()?;
431
432 let token = DataHolder::new(class_id::REQUEST)
433 .with_property(prop::PSK_ID, psk_id.clone())
434 .with_property(prop::KAGREE_ALGO, "PSK")
435 .with_binary_property(prop::CHALLENGE1, challenge1.to_vec())
436 .to_cdr_le();
437
438 let handle = HandshakeHandle(self.next_id());
439 self.pending_initiator.insert(
440 handle,
441 InitiatorState {
442 local: initiator,
443 psk_id,
444 challenge1,
445 },
446 );
447 Ok((handle, HandshakeStepOutcome::SendMessage { token }))
448 }
449
450 fn begin_handshake_reply(
451 &mut self,
452 replier: IdentityHandle,
453 _initiator: IdentityHandle,
454 request_token: &[u8],
455 ) -> SecurityResult<(HandshakeHandle, HandshakeStepOutcome)> {
456 let dh = DataHolder::from_cdr_le(request_token)?;
457 if dh.class_id != class_id::REQUEST {
458 return Err(SecurityError::new(
459 SecurityErrorKind::AuthenticationFailed,
460 alloc::format!(
461 "psk: reply expected request, got class_id '{}'",
462 dh.class_id
463 ),
464 ));
465 }
466 let psk_id = dh
467 .property(prop::PSK_ID)
468 .ok_or_else(|| {
469 SecurityError::new(
470 SecurityErrorKind::AuthenticationFailed,
471 "psk: request missing psk.id",
472 )
473 })?
474 .to_string();
475 let challenge1 = read_32(&dh, prop::CHALLENGE1)?;
476
477 self.record_challenge(replier, challenge1)?;
479
480 let psk = self.lookup_psk(&psk_id)?.to_vec();
482
483 let challenge2 = random_challenge()?;
484 let hmac = hmac_sign(&psk, &psk_id, &challenge1, &challenge2);
485 let secret = derive_psk_shared_secret(&psk, &challenge1, &challenge2)?;
486
487 let token = DataHolder::new(class_id::REPLY)
488 .with_property(prop::PSK_ID, psk_id.clone())
489 .with_property(prop::KAGREE_ALGO, "PSK")
490 .with_binary_property(prop::CHALLENGE1, challenge1.to_vec())
491 .with_binary_property(prop::CHALLENGE2, challenge2.to_vec())
492 .with_binary_property(prop::HMAC, hmac.to_vec())
493 .to_cdr_le();
494
495 let secret_handle = self.store_secret(secret.to_vec());
496 let handle = HandshakeHandle(self.next_id());
497 self.handshake_to_secret.insert(handle, secret_handle);
498 self.pending_replier.insert(
499 handle,
500 ReplierState {
501 local: replier,
502 psk_id,
503 challenge1,
504 challenge2,
505 secret_handle,
506 },
507 );
508 Ok((handle, HandshakeStepOutcome::SendMessage { token }))
509 }
510
511 fn process_handshake(
512 &mut self,
513 handshake: HandshakeHandle,
514 token: &[u8],
515 ) -> SecurityResult<HandshakeStepOutcome> {
516 if self.pending_initiator.contains_key(&handshake) {
517 return self.process_reply_on_initiator(handshake, token);
518 }
519 if self.pending_replier.contains_key(&handshake) {
520 return self.process_final_on_replier(handshake, token);
521 }
522 Err(SecurityError::new(
523 SecurityErrorKind::BadArgument,
524 "psk: unbekannter HandshakeHandle",
525 ))
526 }
527
528 fn shared_secret(&self, handshake: HandshakeHandle) -> SecurityResult<SharedSecretHandle> {
529 self.handshake_to_secret
530 .get(&handshake)
531 .copied()
532 .ok_or_else(|| {
533 SecurityError::new(
534 SecurityErrorKind::BadArgument,
535 "psk: handshake-handle unbekannt oder noch nicht completed",
536 )
537 })
538 }
539
540 fn plugin_class_id(&self) -> &str {
541 class_id::PSK
542 }
543}
544
545impl PskAuthenticationPlugin {
546 fn process_reply_on_initiator(
547 &mut self,
548 handshake: HandshakeHandle,
549 token: &[u8],
550 ) -> SecurityResult<HandshakeStepOutcome> {
551 let dh = DataHolder::from_cdr_le(token)?;
552 if dh.class_id != class_id::REPLY {
553 return Err(SecurityError::new(
554 SecurityErrorKind::AuthenticationFailed,
555 alloc::format!("psk: process expected reply, got '{}'", dh.class_id),
556 ));
557 }
558 let st = self.pending_initiator.remove(&handshake).ok_or_else(|| {
559 SecurityError::new(SecurityErrorKind::BadArgument, "psk: initiator state gone")
560 })?;
561 let psk_id_in = dh
562 .property(prop::PSK_ID)
563 .ok_or_else(|| {
564 SecurityError::new(
565 SecurityErrorKind::AuthenticationFailed,
566 "psk: reply missing psk.id",
567 )
568 })?
569 .to_string();
570 if psk_id_in != st.psk_id {
571 return Err(SecurityError::new(
572 SecurityErrorKind::AuthenticationFailed,
573 "psk: psk.id echo mismatch in reply",
574 ));
575 }
576 let ch1 = read_32(&dh, prop::CHALLENGE1)?;
577 if ch1 != st.challenge1 {
578 return Err(SecurityError::new(
579 SecurityErrorKind::AuthenticationFailed,
580 "psk: challenge1 echo mismatch",
581 ));
582 }
583 let ch2 = read_32(&dh, prop::CHALLENGE2)?;
584 let hmac = dh.binary_property(prop::HMAC).ok_or_else(|| {
585 SecurityError::new(
586 SecurityErrorKind::AuthenticationFailed,
587 "psk: reply missing hmac",
588 )
589 })?;
590
591 let psk = self.lookup_psk(&st.psk_id)?.to_vec();
592 hmac_verify(&psk, &st.psk_id, &ch1, &ch2, hmac)?;
593
594 let final_hmac = hmac_sign(&psk, &st.psk_id, &ch1, &ch2);
596 let secret = derive_psk_shared_secret(&psk, &ch1, &ch2)?;
597 let secret_handle = self.store_secret(secret.to_vec());
598 self.handshake_to_secret.insert(handshake, secret_handle);
599
600 let final_token = DataHolder::new(class_id::FINAL)
601 .with_binary_property(prop::CHALLENGE1, ch1.to_vec())
602 .with_binary_property(prop::CHALLENGE2, ch2.to_vec())
603 .with_binary_property(prop::HMAC, final_hmac.to_vec())
604 .to_cdr_le();
605
606 let _ = st.local;
607 Ok(HandshakeStepOutcome::SendMessage { token: final_token })
608 }
609
610 fn process_final_on_replier(
611 &mut self,
612 handshake: HandshakeHandle,
613 token: &[u8],
614 ) -> SecurityResult<HandshakeStepOutcome> {
615 let dh = DataHolder::from_cdr_le(token)?;
616 if dh.class_id != class_id::FINAL {
617 return Err(SecurityError::new(
618 SecurityErrorKind::AuthenticationFailed,
619 alloc::format!("psk: process expected final, got '{}'", dh.class_id),
620 ));
621 }
622 let st = self.pending_replier.remove(&handshake).ok_or_else(|| {
623 SecurityError::new(SecurityErrorKind::BadArgument, "psk: replier state gone")
624 })?;
625 let ch1 = read_32(&dh, prop::CHALLENGE1)?;
626 let ch2 = read_32(&dh, prop::CHALLENGE2)?;
627 if ch1 != st.challenge1 || ch2 != st.challenge2 {
628 return Err(SecurityError::new(
629 SecurityErrorKind::AuthenticationFailed,
630 "psk: final challenge echo mismatch",
631 ));
632 }
633 let hmac = dh.binary_property(prop::HMAC).ok_or_else(|| {
634 SecurityError::new(
635 SecurityErrorKind::AuthenticationFailed,
636 "psk: final missing hmac",
637 )
638 })?;
639 let psk = self.lookup_psk(&st.psk_id)?.to_vec();
640 hmac_verify(&psk, &st.psk_id, &ch1, &ch2, hmac)?;
641
642 let _ = st.local;
643 Ok(HandshakeStepOutcome::Complete {
644 secret: st.secret_handle,
645 })
646 }
647}
648
649fn hex_decode(s: &str) -> SecurityResult<Vec<u8>> {
650 if s.len() % 2 != 0 {
651 return Err(SecurityError::new(
652 SecurityErrorKind::BadArgument,
653 "psk: hex string with odd length",
654 ));
655 }
656 let mut out = Vec::with_capacity(s.len() / 2);
657 let bytes = s.as_bytes();
658 for chunk in bytes.chunks(2) {
659 let hi = hex_nibble(chunk[0])?;
660 let lo = hex_nibble(chunk[1])?;
661 out.push(hi * 16 + lo);
666 }
667 Ok(out)
668}
669
670fn hex_nibble(c: u8) -> SecurityResult<u8> {
671 match c {
672 b'0'..=b'9' => Ok(c - b'0'),
673 b'a'..=b'f' => Ok(c - b'a' + 10),
674 b'A'..=b'F' => Ok(c - b'A' + 10),
675 _ => Err(SecurityError::new(
676 SecurityErrorKind::BadArgument,
677 "psk: invalid hex nibble",
678 )),
679 }
680}
681
682#[cfg(test)]
683#[allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
684mod tests {
685 use super::*;
686 use zerodds_security::properties::Property;
687
688 fn alice_bob_with_shared_psk() -> (
689 PskAuthenticationPlugin,
690 PskAuthenticationPlugin,
691 IdentityHandle,
692 IdentityHandle,
693 ) {
694 let psk = alloc::vec![0xA5u8; 32];
695 let mut alice = PskAuthenticationPlugin::new();
696 let mut bob = PskAuthenticationPlugin::new();
697 alice.register_psk("alice-bob".into(), psk.clone()).unwrap();
698 bob.register_psk("alice-bob".into(), psk).unwrap();
699 let alice_h = alice.validate_local_psk_identity("alice-bob").unwrap();
700 let bob_h = bob.validate_local_psk_identity("alice-bob").unwrap();
701 (alice, bob, alice_h, bob_h)
702 }
703
704 #[test]
705 fn plugin_class_id_matches_spec() {
706 let p = PskAuthenticationPlugin::new();
707 assert_eq!(p.plugin_class_id(), "DDS:Auth:PSK:1.2");
708 }
709
710 #[test]
711 fn token_class_ids_match_spec() {
712 assert_eq!(class_id::PSK, "DDS:Auth:PSK:1.2");
713 assert_eq!(class_id::REQUEST, "DDS:Auth:PSK:1.2+AuthReq");
714 assert_eq!(class_id::REPLY, "DDS:Auth:PSK:1.2+AuthReply");
715 assert_eq!(class_id::FINAL, "DDS:Auth:PSK:1.2+AuthFinal");
716 }
717
718 #[test]
719 fn register_psk_then_validate_local_happy_path() {
720 let mut p = PskAuthenticationPlugin::new();
721 p.register_psk("client-1".into(), alloc::vec![0x11; 32])
722 .unwrap();
723 let h = p.validate_local_psk_identity("client-1").unwrap();
724 assert!(h.0 >= 1);
725 }
726
727 #[test]
728 fn validate_local_unknown_id_rejected() {
729 let mut p = PskAuthenticationPlugin::new();
730 let err = p.validate_local_psk_identity("ghost").unwrap_err();
731 assert_eq!(err.kind, SecurityErrorKind::BadArgument);
732 }
733
734 #[test]
735 fn register_psk_rejects_empty_key() {
736 let mut p = PskAuthenticationPlugin::new();
737 let err = p.register_psk("x".into(), Vec::new()).unwrap_err();
738 assert_eq!(err.kind, SecurityErrorKind::BadArgument);
739 }
740
741 #[test]
742 fn register_psk_rejects_empty_id() {
743 let mut p = PskAuthenticationPlugin::new();
744 let err = p
745 .register_psk(String::new(), alloc::vec![1, 2, 3])
746 .unwrap_err();
747 assert_eq!(err.kind, SecurityErrorKind::BadArgument);
748 }
749
750 #[test]
751 fn register_psk_replace_semantics_last_wins() {
752 let mut p = PskAuthenticationPlugin::new();
753 p.register_psk("k".into(), alloc::vec![1; 32]).unwrap();
754 p.register_psk("k".into(), alloc::vec![2; 32]).unwrap();
755 let key = p.psks.get("k").unwrap();
756 assert_eq!(key, &alloc::vec![2u8; 32]);
757 }
758
759 #[test]
760 fn full_three_round_handshake_alice_bob() {
761 let (mut alice, mut bob, alice_h, bob_h) = alice_bob_with_shared_psk();
762
763 let (alice_hs, out1) = alice.begin_handshake_request(alice_h, bob_h).unwrap();
764 let req = match out1 {
765 HandshakeStepOutcome::SendMessage { token } => token,
766 _ => panic!("expected SendMessage"),
767 };
768
769 let (bob_hs, out2) = bob.begin_handshake_reply(bob_h, alice_h, &req).unwrap();
770 let reply = match out2 {
771 HandshakeStepOutcome::SendMessage { token } => token,
772 _ => panic!("expected SendMessage"),
773 };
774
775 let out3 = alice.process_handshake(alice_hs, &reply).unwrap();
776 let final_tok = match out3 {
777 HandshakeStepOutcome::SendMessage { token } => token,
778 _ => panic!("expected SendMessage"),
779 };
780
781 let out4 = bob.process_handshake(bob_hs, &final_tok).unwrap();
782 let bob_secret = match out4 {
783 HandshakeStepOutcome::Complete { secret } => secret,
784 _ => panic!("expected Complete"),
785 };
786
787 let alice_secret = alice.shared_secret(alice_hs).unwrap();
788 let a_bytes = alice.secret_bytes(alice_secret).unwrap();
789 let b_bytes = bob.secret_bytes(bob_secret).unwrap();
790 assert_eq!(a_bytes.len(), 32);
791 assert_eq!(
792 a_bytes, b_bytes,
793 "alice + bob muessen gleiches secret haben"
794 );
795 }
796
797 #[test]
798 fn tampered_reply_hmac_rejected_by_initiator() {
799 let (mut alice, mut bob, alice_h, bob_h) = alice_bob_with_shared_psk();
800 let (alice_hs, out1) = alice.begin_handshake_request(alice_h, bob_h).unwrap();
801 let req = match out1 {
802 HandshakeStepOutcome::SendMessage { token } => token,
803 _ => panic!(),
804 };
805 let (_, out2) = bob.begin_handshake_reply(bob_h, alice_h, &req).unwrap();
806 let mut reply = match out2 {
807 HandshakeStepOutcome::SendMessage { token } => token,
808 _ => panic!(),
809 };
810 let mut h = DataHolder::from_cdr_le(&reply).unwrap();
811 let mut hmac = h.binary_property(prop::HMAC).unwrap().to_vec();
812 hmac[0] ^= 0x01;
813 h.set_binary_property(prop::HMAC, hmac);
814 reply = h.to_cdr_le();
815 let err = alice.process_handshake(alice_hs, &reply).unwrap_err();
816 assert_eq!(err.kind, SecurityErrorKind::AuthenticationFailed);
817 }
818
819 #[test]
820 fn replay_initiator_request_rejected_second_time() {
821 let (mut alice, mut bob, alice_h, bob_h) = alice_bob_with_shared_psk();
822 let (_alice_hs, out1) = alice.begin_handshake_request(alice_h, bob_h).unwrap();
823 let req = match out1 {
824 HandshakeStepOutcome::SendMessage { token } => token,
825 _ => panic!(),
826 };
827 bob.begin_handshake_reply(bob_h, alice_h, &req).unwrap();
828 let err = bob.begin_handshake_reply(bob_h, alice_h, &req).unwrap_err();
829 assert_eq!(err.kind, SecurityErrorKind::AuthenticationFailed);
830 }
831
832 #[test]
833 fn wrong_psk_on_replier_breaks_hmac_on_initiator() {
834 let mut alice = PskAuthenticationPlugin::new();
835 let mut bob = PskAuthenticationPlugin::new();
836 alice
837 .register_psk("k".into(), alloc::vec![0xAAu8; 32])
838 .unwrap();
839 bob.register_psk("k".into(), alloc::vec![0xBBu8; 32])
841 .unwrap();
842 let alice_h = alice.validate_local_psk_identity("k").unwrap();
843 let bob_h = bob.validate_local_psk_identity("k").unwrap();
844
845 let (alice_hs, out1) = alice.begin_handshake_request(alice_h, bob_h).unwrap();
846 let req = match out1 {
847 HandshakeStepOutcome::SendMessage { token } => token,
848 _ => panic!(),
849 };
850 let (_, out2) = bob.begin_handshake_reply(bob_h, alice_h, &req).unwrap();
851 let reply = match out2 {
852 HandshakeStepOutcome::SendMessage { token } => token,
853 _ => panic!(),
854 };
855 let err = alice.process_handshake(alice_hs, &reply).unwrap_err();
856 assert_eq!(err.kind, SecurityErrorKind::AuthenticationFailed);
857 }
858
859 #[test]
860 fn unknown_psk_id_in_request_rejected_by_replier() {
861 let mut bob = PskAuthenticationPlugin::new();
862 bob.register_psk("known".into(), alloc::vec![0x11; 32])
863 .unwrap();
864 let bob_h = bob.validate_local_psk_identity("known").unwrap();
865 let req = DataHolder::new(class_id::REQUEST)
867 .with_property(prop::PSK_ID, "unknown")
868 .with_property(prop::KAGREE_ALGO, "PSK")
869 .with_binary_property(prop::CHALLENGE1, alloc::vec![0u8; 32])
870 .to_cdr_le();
871 let err = bob
872 .begin_handshake_reply(bob_h, IdentityHandle(99), &req)
873 .unwrap_err();
874 assert_eq!(err.kind, SecurityErrorKind::AuthenticationFailed);
875 }
876
877 #[test]
878 fn truncated_request_rejected() {
879 let mut bob = PskAuthenticationPlugin::new();
880 bob.register_psk("k".into(), alloc::vec![0x11; 32]).unwrap();
881 let bob_h = bob.validate_local_psk_identity("k").unwrap();
882 let err = bob
883 .begin_handshake_reply(bob_h, IdentityHandle(99), &[0u8, 1, 2])
884 .unwrap_err();
885 assert_eq!(err.kind, SecurityErrorKind::BadArgument);
886 }
887
888 #[test]
889 fn validate_remote_token_happy_path() {
890 let mut p = PskAuthenticationPlugin::new();
891 p.register_psk("peer-1".into(), alloc::vec![0xCCu8; 32])
892 .unwrap();
893 let local = p.validate_local_psk_identity("peer-1").unwrap();
894 let token = p.build_identity_token(local).unwrap();
895 let remote = p.validate_remote_psk_identity(&token).unwrap();
896 assert_ne!(remote, local);
897 }
898
899 #[test]
900 fn validate_remote_token_rejects_unknown_id() {
901 let mut p = PskAuthenticationPlugin::new();
902 p.register_psk("known".into(), alloc::vec![0xCCu8; 32])
903 .unwrap();
904 let token = DataHolder::new(class_id::PSK)
905 .with_property(PROP_PSK_ID, "stranger")
906 .to_cdr_le();
907 let err = p.validate_remote_psk_identity(&token).unwrap_err();
908 assert_eq!(err.kind, SecurityErrorKind::AuthenticationFailed);
909 }
910
911 #[test]
912 fn validate_remote_token_rejects_wrong_class_id() {
913 let mut p = PskAuthenticationPlugin::new();
914 p.register_psk("k".into(), alloc::vec![0x1u8; 32]).unwrap();
915 let token = DataHolder::new("DDS:Auth:PKI-DH:1.2")
916 .with_property(PROP_PSK_ID, "k")
917 .to_cdr_le();
918 let err = p.validate_remote_psk_identity(&token).unwrap_err();
919 assert_eq!(err.kind, SecurityErrorKind::AuthenticationFailed);
920 }
921
922 #[test]
923 fn cross_plugin_psk_vs_pki_mismatch_class_id() {
924 let mut psk = PskAuthenticationPlugin::new();
927 psk.register_psk("k".into(), alloc::vec![0xAA; 32]).unwrap();
928 let h = psk.validate_local_psk_identity("k").unwrap();
929 let psk_token = psk.build_identity_token(h).unwrap();
930 let dh = DataHolder::from_cdr_le(&psk_token).unwrap();
931 assert_eq!(dh.class_id, "DDS:Auth:PSK:1.2");
932 assert_ne!(dh.class_id, "DDS:Auth:PKI-DH:1.2");
933 }
934
935 #[test]
936 fn token_roundtrip_via_data_holder_codec() {
937 let mut p = PskAuthenticationPlugin::new();
938 p.register_psk("alpha".into(), alloc::vec![0xBE; 32])
939 .unwrap();
940 let h = p.validate_local_psk_identity("alpha").unwrap();
941 let token = p.build_identity_token(h).unwrap();
942 let dh = DataHolder::from_cdr_le(&token).unwrap();
943 assert_eq!(dh.class_id, class_id::PSK);
944 assert_eq!(dh.property(PROP_PSK_ID), Some("alpha"));
945 }
946
947 #[test]
948 fn validate_local_via_property_list_with_inline_hex_key() {
949 let mut p = PskAuthenticationPlugin::new();
950 let key_hex: String = (0..32).map(|_| "ab").collect();
951 let props = PropertyList::new()
952 .with(Property::local(PROP_PSK_ID, "node-1"))
953 .with(Property::local(PROP_PSK_KEY_HEX, key_hex));
954 let h = p.validate_local_identity(&props, [0xAA; 16]).unwrap();
955 assert!(h.0 >= 1);
956 }
957
958 #[test]
959 fn validate_local_via_property_list_missing_id_rejected() {
960 let mut p = PskAuthenticationPlugin::new();
961 let props = PropertyList::new();
962 let err = p.validate_local_identity(&props, [0xAA; 16]).unwrap_err();
963 assert_eq!(err.kind, SecurityErrorKind::InvalidConfiguration);
964 }
965
966 #[test]
967 fn shared_secret_returns_bad_argument_for_unknown_handle() {
968 let p = PskAuthenticationPlugin::new();
969 let err = p.shared_secret(HandshakeHandle(42)).unwrap_err();
970 assert_eq!(err.kind, SecurityErrorKind::BadArgument);
971 }
972
973 #[test]
974 fn hkdf_test_vector_rfc5869_ish_is_deterministic() {
975 let psk = alloc::vec![0x0bu8; 22];
977 let ch1 = [0x01u8; 32];
978 let ch2 = [0x02u8; 32];
979 let s1 = derive_psk_shared_secret(&psk, &ch1, &ch2).unwrap();
980 let s2 = derive_psk_shared_secret(&psk, &ch1, &ch2).unwrap();
981 assert_eq!(s1, s2);
982 let s3 = derive_psk_shared_secret(&psk, &ch2, &ch1).unwrap();
984 assert_ne!(s1, s3);
985 }
986
987 #[test]
988 fn shared_secret_is_32_bytes() {
989 let psk = alloc::vec![0xFFu8; 16];
990 let s = derive_psk_shared_secret(&psk, &[0u8; 32], &[1u8; 32]).unwrap();
991 assert_eq!(s.len(), 32);
992 }
993
994 #[test]
995 fn process_handshake_unknown_handle_rejected() {
996 let mut p = PskAuthenticationPlugin::new();
997 let err = p.process_handshake(HandshakeHandle(999), &[]).unwrap_err();
998 assert_eq!(err.kind, SecurityErrorKind::BadArgument);
999 }
1000
1001 #[test]
1002 fn final_token_validates_initiator_hmac_on_replier() {
1003 let (mut alice, mut bob, alice_h, bob_h) = alice_bob_with_shared_psk();
1006 let (alice_hs, req_out) = alice.begin_handshake_request(alice_h, bob_h).unwrap();
1007 let req = match req_out {
1008 HandshakeStepOutcome::SendMessage { token } => token,
1009 _ => panic!(),
1010 };
1011 let (bob_hs, reply_out) = bob.begin_handshake_reply(bob_h, alice_h, &req).unwrap();
1012 let reply = match reply_out {
1013 HandshakeStepOutcome::SendMessage { token } => token,
1014 _ => panic!(),
1015 };
1016 let final_out = alice.process_handshake(alice_hs, &reply).unwrap();
1017 let mut final_tok = match final_out {
1018 HandshakeStepOutcome::SendMessage { token } => token,
1019 _ => panic!(),
1020 };
1021 let mut h = DataHolder::from_cdr_le(&final_tok).unwrap();
1022 let mut hm = h.binary_property(prop::HMAC).unwrap().to_vec();
1023 hm[5] ^= 0xFF;
1024 h.set_binary_property(prop::HMAC, hm);
1025 final_tok = h.to_cdr_le();
1026 let err = bob.process_handshake(bob_hs, &final_tok).unwrap_err();
1027 assert_eq!(err.kind, SecurityErrorKind::AuthenticationFailed);
1028 }
1029
1030 #[test]
1031 fn request_token_carries_kagree_psk() {
1032 let (mut alice, _bob, alice_h, _bob_h) = alice_bob_with_shared_psk();
1033 let (_, out) = alice
1034 .begin_handshake_request(alice_h, IdentityHandle(99))
1035 .unwrap();
1036 let token = match out {
1037 HandshakeStepOutcome::SendMessage { token } => token,
1038 _ => panic!(),
1039 };
1040 let dh = DataHolder::from_cdr_le(&token).unwrap();
1041 assert_eq!(dh.class_id, class_id::REQUEST);
1042 assert_eq!(dh.property(prop::KAGREE_ALGO), Some("PSK"));
1043 }
1044
1045 #[test]
1046 fn shared_secret_provider_returns_bytes_after_handshake() {
1047 let (mut alice, mut bob, alice_h, bob_h) = alice_bob_with_shared_psk();
1048 let (alice_hs, out1) = alice.begin_handshake_request(alice_h, bob_h).unwrap();
1049 let req = match out1 {
1050 HandshakeStepOutcome::SendMessage { token } => token,
1051 _ => panic!(),
1052 };
1053 let (bob_hs, out2) = bob.begin_handshake_reply(bob_h, alice_h, &req).unwrap();
1054 let reply = match out2 {
1055 HandshakeStepOutcome::SendMessage { token } => token,
1056 _ => panic!(),
1057 };
1058 let out3 = alice.process_handshake(alice_hs, &reply).unwrap();
1059 let final_tok = match out3 {
1060 HandshakeStepOutcome::SendMessage { token } => token,
1061 _ => panic!(),
1062 };
1063 bob.process_handshake(bob_hs, &final_tok).unwrap();
1064
1065 let alice_secret = alice.shared_secret(alice_hs).unwrap();
1066 let provider: &dyn SharedSecretProvider = &alice;
1067 let bytes = provider.get_shared_secret(alice_secret).unwrap();
1068 assert_eq!(bytes.len(), 32);
1069 }
1070
1071 #[test]
1072 fn hex_decode_round_trips_simple_input() {
1073 let v = hex_decode("0a0b").unwrap();
1074 assert_eq!(v, alloc::vec![0x0a, 0x0b]);
1075 }
1076
1077 #[test]
1078 fn hex_decode_rejects_odd_len() {
1079 let err = hex_decode("abc").unwrap_err();
1080 assert_eq!(err.kind, SecurityErrorKind::BadArgument);
1081 }
1082
1083 #[test]
1084 fn hex_decode_rejects_non_hex() {
1085 let err = hex_decode("zz").unwrap_err();
1086 assert_eq!(err.kind, SecurityErrorKind::BadArgument);
1087 }
1088
1089 #[test]
1096 fn psk_replay_cache_holds_exactly_cap() {
1097 let (mut alice, _, alice_h, _) = alice_bob_with_shared_psk();
1098 for i in 0..REPLAY_CACHE_CAP {
1099 let mut c = [0u8; 32];
1100 c[0..8].copy_from_slice(&(i as u64).to_le_bytes());
1101 alice.record_challenge(alice_h, c).unwrap();
1102 }
1103 let mut c_first = [0u8; 32];
1104 c_first[0..8].copy_from_slice(&0u64.to_le_bytes());
1105 let err = alice.record_challenge(alice_h, c_first).unwrap_err();
1106 assert_eq!(err.kind, SecurityErrorKind::AuthenticationFailed);
1107 }
1108
1109 #[test]
1110 fn psk_replay_cache_evicts_at_cap_plus_one() {
1111 let (mut alice, _, alice_h, _) = alice_bob_with_shared_psk();
1112 for i in 0..REPLAY_CACHE_CAP {
1113 let mut c = [0u8; 32];
1114 c[0..8].copy_from_slice(&(i as u64).to_le_bytes());
1115 alice.record_challenge(alice_h, c).unwrap();
1116 }
1117 let mut c_extra = [0u8; 32];
1118 c_extra[0..8].copy_from_slice(&(REPLAY_CACHE_CAP as u64).to_le_bytes());
1119 alice.record_challenge(alice_h, c_extra).unwrap();
1120 let mut c0 = [0u8; 32];
1121 c0[0..8].copy_from_slice(&0u64.to_le_bytes());
1122 alice
1123 .record_challenge(alice_h, c0)
1124 .expect("oldest should be evicted");
1125 }
1126
1127 #[test]
1130 fn hmac_input_length_prefixed_concatenation() {
1131 let psk_id = "abc";
1132 let ch1 = [0x11u8; 32];
1133 let ch2 = [0x22u8; 32];
1134 let out = hmac_input(psk_id, &ch1, &ch2);
1135 assert_eq!(out.len(), 4 + 3 + 4 + 32 + 4 + 32);
1138 assert_eq!(&out[0..4], &3u32.to_le_bytes());
1140 assert_eq!(&out[4..7], b"abc");
1142 assert_eq!(&out[7..11], &32u32.to_le_bytes());
1144 assert_eq!(&out[11..43], &ch1[..]);
1146 assert_eq!(&out[43..47], &32u32.to_le_bytes());
1148 assert_eq!(&out[47..79], &ch2[..]);
1150
1151 let out_b = hmac_input("xyz", &ch1, &ch2);
1153 assert_ne!(out, out_b);
1154 }
1155
1156 #[test]
1159 fn hex_nibble_all_three_ranges() {
1160 for (c, expected) in [
1161 (b'0', 0u8),
1162 (b'5', 5),
1163 (b'9', 9),
1164 (b'a', 10),
1165 (b'c', 12),
1166 (b'f', 15),
1167 (b'A', 10),
1168 (b'C', 12),
1169 (b'F', 15),
1170 ] {
1171 assert_eq!(hex_nibble(c).unwrap(), expected, "char {c:#x}");
1172 }
1173 assert!(hex_nibble(b'g').is_err());
1174 assert!(hex_nibble(b'G').is_err());
1175 assert!(hex_nibble(b'@').is_err());
1176 }
1177
1178 #[test]
1181 fn hex_decode_specific_byte_values() {
1182 assert_eq!(hex_decode("ab").unwrap(), vec![0xAB]);
1184 assert_eq!(hex_decode("F0").unwrap(), vec![0xF0]);
1186 assert_eq!(hex_decode("0F").unwrap(), vec![0x0F]);
1187 assert_eq!(
1188 hex_decode("DEADBEEF").unwrap(),
1189 vec![0xDE, 0xAD, 0xBE, 0xEF]
1190 );
1191 }
1192
1193 fn run_psk_final_tampered<M>(mutate: M)
1196 where
1197 M: FnOnce(&mut DataHolder),
1198 {
1199 let (mut alice, mut bob, alice_h, bob_h) = alice_bob_with_shared_psk();
1200 let (alice_hs, out1) = alice.begin_handshake_request(alice_h, bob_h).unwrap();
1201 let req = match out1 {
1202 HandshakeStepOutcome::SendMessage { token } => token,
1203 _ => panic!(),
1204 };
1205 let (bob_hs, out2) = bob.begin_handshake_reply(bob_h, alice_h, &req).unwrap();
1206 let reply = match out2 {
1207 HandshakeStepOutcome::SendMessage { token } => token,
1208 _ => panic!(),
1209 };
1210 let out3 = alice.process_handshake(alice_hs, &reply).unwrap();
1211 let final_tok = match out3 {
1212 HandshakeStepOutcome::SendMessage { token } => token,
1213 _ => panic!(),
1214 };
1215 let mut h = DataHolder::from_cdr_le(&final_tok).unwrap();
1216 mutate(&mut h);
1217 let tampered = h.to_cdr_le();
1218 let err = bob.process_handshake(bob_hs, &tampered).unwrap_err();
1219 assert_eq!(err.kind, SecurityErrorKind::AuthenticationFailed);
1220 }
1221
1222 #[test]
1223 fn psk_final_challenge1_tamper_rejected() {
1224 run_psk_final_tampered(|h| {
1225 let mut v = h.binary_property("challenge1").unwrap().to_vec();
1226 v[0] ^= 0x01;
1227 h.set_binary_property("challenge1", v);
1228 });
1229 }
1230 #[test]
1231 fn psk_final_challenge2_tamper_rejected() {
1232 run_psk_final_tampered(|h| {
1233 let mut v = h.binary_property("challenge2").unwrap().to_vec();
1234 v[0] ^= 0x01;
1235 h.set_binary_property("challenge2", v);
1236 });
1237 }
1238
1239 #[test]
1248 fn psk_final_only_ch1_tamper_with_recomputed_hmac_rejected() {
1249 let psk_bytes = alloc::vec![0xA5u8; 32];
1250 let psk_id = "alice-bob";
1251
1252 let (mut alice, mut bob, alice_h, bob_h) = alice_bob_with_shared_psk();
1253 let (alice_hs, out1) = alice.begin_handshake_request(alice_h, bob_h).unwrap();
1254 let req = match out1 {
1255 HandshakeStepOutcome::SendMessage { token } => token,
1256 _ => panic!(),
1257 };
1258 let (bob_hs, out2) = bob.begin_handshake_reply(bob_h, alice_h, &req).unwrap();
1259 let reply = match out2 {
1260 HandshakeStepOutcome::SendMessage { token } => token,
1261 _ => panic!(),
1262 };
1263 let out3 = alice.process_handshake(alice_hs, &reply).unwrap();
1264 let final_tok = match out3 {
1265 HandshakeStepOutcome::SendMessage { token } => token,
1266 _ => panic!(),
1267 };
1268
1269 let mut h = DataHolder::from_cdr_le(&final_tok).unwrap();
1271 let mut new_ch1 = h.binary_property("challenge1").unwrap().to_vec();
1272 new_ch1[0] ^= 0x01;
1273 let ch2_vec = h.binary_property("challenge2").unwrap().to_vec();
1274
1275 let mut ch1_arr = [0u8; 32];
1276 ch1_arr.copy_from_slice(&new_ch1);
1277 let mut ch2_arr = [0u8; 32];
1278 ch2_arr.copy_from_slice(&ch2_vec);
1279 let new_hmac = hmac_sign(&psk_bytes, psk_id, &ch1_arr, &ch2_arr);
1280
1281 h.set_binary_property("challenge1", new_ch1);
1282 h.set_binary_property("hmac", new_hmac.to_vec());
1283 let tampered = h.to_cdr_le();
1284
1285 let err = bob.process_handshake(bob_hs, &tampered).unwrap_err();
1289 assert_eq!(err.kind, SecurityErrorKind::AuthenticationFailed);
1290 }
1291}