1use alloc::collections::{BTreeMap, BTreeSet};
16use alloc::vec::Vec;
17use core::sync::atomic::{AtomicU64, Ordering};
18
19use crate::backend::digest;
20use crate::backend::rand::{SecureRandom, SystemRandom};
21use crate::backend::signature;
22use rustls_pki_types::CertificateDer;
23use zerodds_security::authentication::{
24 AuthenticationPlugin, HandshakeHandle, HandshakeStepOutcome, IdentityHandle,
25 SharedSecretHandle, SharedSecretProvider,
26};
27use zerodds_security::error::{SecurityError, SecurityErrorKind, SecurityResult};
28use zerodds_security::properties::PropertyList;
29use zerodds_security_keyexchange::{KeyExchange, KxSuite};
30
31use crate::handshake_token::{
32 self as ht, FinalBuildInput, ReplyBuildInput, RequestBuildInput, ct_eq,
33};
34use crate::identity::{CertKeyAlgo, IdentityConfig, ParsedIdentity, PkiError};
35
36mod keys {
40 pub const IDENTITY_CERT: &str = "dds.sec.auth.identity_certificate";
44 pub const IDENTITY_CA: &str = "dds.sec.auth.identity_ca";
46 pub const IDENTITY_KEY: &str = "dds.sec.auth.private_key";
48}
49
50const REPLAY_CACHE_CAP: usize = 1024;
53
54pub struct PkiAuthenticationPlugin {
65 next_handle: AtomicU64,
66 identities: BTreeMap<IdentityHandle, ParsedIdentity>,
67 pending_initiator: BTreeMap<HandshakeHandle, InitiatorState>,
69 pending_replier: BTreeMap<HandshakeHandle, ReplierState>,
71 handshake_to_secret: BTreeMap<HandshakeHandle, SharedSecretHandle>,
73 secrets: BTreeMap<SharedSecretHandle, Vec<u8>>,
75 secret_challenges: BTreeMap<SharedSecretHandle, ([u8; 32], [u8; 32])>,
78 replay_cache: BTreeMap<IdentityHandle, BTreeSet<[u8; 32]>>,
81 replay_order: BTreeMap<IdentityHandle, Vec<[u8; 32]>>,
83 preferred_kx_suite: KxSuite,
88 algo_nul: bool,
95 local_permissions: Vec<u8>,
100 local_pdata: Vec<u8>,
105}
106
107struct InitiatorState {
108 local: IdentityHandle,
110 kx: Option<KeyExchange>,
112 dh1: Vec<u8>,
114 challenge1: [u8; 32],
116 hash_c1: [u8; 32],
118 #[allow(dead_code)]
122 permissions: Vec<u8>,
123 #[allow(dead_code)]
125 pdata: Vec<u8>,
126 kagree_algo: alloc::string::String,
128 #[allow(dead_code)]
132 dsign_algo: alloc::string::String,
133}
134
135struct ReplierState {
136 local: IdentityHandle,
138 challenge1: [u8; 32],
140 challenge2: [u8; 32],
142 dh1: Vec<u8>,
144 dh2: Vec<u8>,
146 hash_c1: [u8; 32],
148 hash_c2: [u8; 32],
150 secret_handle: SharedSecretHandle,
152 initiator_cert_der: Vec<u8>,
154 initiator_key_algo: CertKeyAlgo,
156}
157
158impl Default for PkiAuthenticationPlugin {
159 fn default() -> Self {
160 Self::new()
161 }
162}
163
164impl PkiAuthenticationPlugin {
165 #[must_use]
167 pub fn new() -> Self {
168 Self {
169 next_handle: AtomicU64::new(0),
170 identities: BTreeMap::new(),
171 pending_initiator: BTreeMap::new(),
172 pending_replier: BTreeMap::new(),
173 handshake_to_secret: BTreeMap::new(),
174 secrets: BTreeMap::new(),
175 secret_challenges: BTreeMap::new(),
176 replay_cache: BTreeMap::new(),
177 replay_order: BTreeMap::new(),
178 preferred_kx_suite: KxSuite::EcdhP256,
180 algo_nul: false,
181 local_permissions: Vec::new(),
182 local_pdata: Vec::new(),
183 }
184 }
185
186 pub fn set_local_permissions(&mut self, permissions_p7s: Vec<u8>) {
191 self.local_permissions = permissions_p7s;
192 }
193
194 pub fn set_preferred_kx_suite(&mut self, suite: KxSuite) {
199 self.preferred_kx_suite = suite;
200 }
201
202 fn algo_str(&self, s: &str) -> alloc::string::String {
206 if self.algo_nul {
207 alloc::format!("{s}\0")
208 } else {
209 s.into()
210 }
211 }
212
213 fn next_id(&self) -> u64 {
214 self.next_handle.fetch_add(1, Ordering::Relaxed) + 1
215 }
216
217 fn kx_suite_for_algo(algo: &str) -> Option<KxSuite> {
222 let algo = algo.trim_end_matches('\0');
223 if algo == ht::algo::ECDHE_P256_SHA256 {
224 Some(KxSuite::EcdhP256)
225 } else if algo == ht::algo::X25519 {
226 Some(KxSuite::X25519)
227 } else {
228 None
229 }
230 }
231
232 fn kagree_algo_str(suite: KxSuite) -> &'static str {
234 match suite {
235 KxSuite::EcdhP256 => ht::algo::ECDHE_P256_SHA256,
236 KxSuite::X25519 => ht::algo::X25519,
237 }
238 }
239
240 pub fn validate_with_config(
247 &mut self,
248 cfg: IdentityConfig,
249 _participant_guid: [u8; 16],
250 ) -> SecurityResult<IdentityHandle> {
251 let parsed = ParsedIdentity::from_config(&cfg).map_err(pki_to_security)?;
252 let handle = IdentityHandle(self.next_id());
253 self.identities.insert(handle, parsed);
254 Ok(handle)
255 }
256
257 #[must_use]
261 pub fn secret_bytes(&self, handle: SharedSecretHandle) -> Option<&[u8]> {
262 self.secrets.get(&handle).map(Vec::as_slice)
263 }
264
265 fn store_secret(
266 &mut self,
267 _h: HandshakeHandle,
268 bytes: Vec<u8>,
269 challenge1: [u8; 32],
270 challenge2: [u8; 32],
271 ) -> SharedSecretHandle {
272 let handle = SharedSecretHandle(self.next_id());
273 self.secrets.insert(handle, bytes);
274 self.secret_challenges
275 .insert(handle, (challenge1, challenge2));
276 handle
277 }
278
279 fn record_challenge(&mut self, local: IdentityHandle, c: [u8; 32]) -> SecurityResult<()> {
280 let cache = self.replay_cache.entry(local).or_default();
281 if cache.contains(&c) {
282 return Err(SecurityError::new(
283 SecurityErrorKind::AuthenticationFailed,
284 "pki: replayed challenge1 detected",
285 ));
286 }
287 cache.insert(c);
288 let order = self.replay_order.entry(local).or_default();
289 order.push(c);
290 if order.len() > REPLAY_CACHE_CAP {
291 let dropped = order.remove(0);
292 cache.remove(&dropped);
293 }
294 Ok(())
295 }
296}
297
298impl SharedSecretProvider for PkiAuthenticationPlugin {
299 fn get_shared_secret(&self, handle: SharedSecretHandle) -> Option<Vec<u8>> {
300 self.secrets.get(&handle).cloned()
301 }
302
303 fn get_shared_secret_challenges(
304 &self,
305 handle: SharedSecretHandle,
306 ) -> Option<([u8; 32], [u8; 32])> {
307 self.secret_challenges.get(&handle).copied()
308 }
309}
310
311fn pki_to_security(e: PkiError) -> SecurityError {
312 let kind = match &e {
313 PkiError::InvalidPem(_) | PkiError::NoCertInPem => SecurityErrorKind::BadArgument,
314 PkiError::CertInvalid(_) => SecurityErrorKind::AuthenticationFailed,
315 PkiError::EmptyTrustAnchors => SecurityErrorKind::InvalidConfiguration,
316 };
317 SecurityError::new(kind, alloc::format!("pki: {e}"))
318}
319
320fn random_challenge() -> SecurityResult<[u8; 32]> {
321 let rng = SystemRandom::new();
322 let mut buf = [0u8; 32];
323 rng.fill(&mut buf).map_err(|_| {
324 SecurityError::new(
325 SecurityErrorKind::CryptoFailed,
326 "pki: SystemRandom not available",
327 )
328 })?;
329 Ok(buf)
330}
331
332fn algo_for(key_algo: CertKeyAlgo) -> SecurityResult<&'static str> {
333 match key_algo {
334 CertKeyAlgo::EcdsaP256Sha256 => Ok(ht::algo::ECDSA_SHA256),
335 CertKeyAlgo::RsaPssSha256 => Ok(ht::algo::RSASSA_PSS_SHA256),
336 CertKeyAlgo::Unknown => Err(SecurityError::new(
337 SecurityErrorKind::InvalidConfiguration,
338 "pki: cert public-key algo unsupported",
339 )),
340 }
341}
342
343fn check_dsign_matches(declared: &str, detected: CertKeyAlgo) -> SecurityResult<()> {
344 let expected = algo_for(detected)?;
345 let declared = declared.trim_end_matches('\0');
348 if !declared.eq_ignore_ascii_case(expected) {
349 return Err(SecurityError::new(
350 SecurityErrorKind::InvalidConfiguration,
351 alloc::format!("pki: c.dsign_algo {declared} doesn't match cert (expected {expected})"),
352 ));
353 }
354 Ok(())
355}
356
357fn sign_with(key_algo: CertKeyAlgo, pkcs8: &[u8], msg: &[u8]) -> SecurityResult<Vec<u8>> {
358 let rng = SystemRandom::new();
359 match key_algo {
360 CertKeyAlgo::EcdsaP256Sha256 => {
361 let key = crate::compat::ecdsa_from_pkcs8(
362 &signature::ECDSA_P256_SHA256_ASN1_SIGNING,
363 pkcs8,
364 &rng,
365 )
366 .map_err(|e| {
367 SecurityError::new(
368 SecurityErrorKind::CryptoFailed,
369 alloc::format!("pki: ecdsa key-parse failed: {e}"),
370 )
371 })?;
372 let sig = key.sign(&rng, msg).map_err(|e| {
373 SecurityError::new(
374 SecurityErrorKind::CryptoFailed,
375 alloc::format!("pki: ecdsa sign failed: {e}"),
376 )
377 })?;
378 Ok(sig.as_ref().to_vec())
379 }
380 CertKeyAlgo::RsaPssSha256 => {
381 let key = signature::RsaKeyPair::from_pkcs8(pkcs8).map_err(|e| {
382 SecurityError::new(
383 SecurityErrorKind::CryptoFailed,
384 alloc::format!("pki: rsa key-parse failed: {e}"),
385 )
386 })?;
387 let mut out = alloc::vec![0u8; crate::compat::rsa_modulus_len(&key)];
388 key.sign(&signature::RSA_PSS_SHA256, &rng, msg, &mut out)
389 .map_err(|e| {
390 SecurityError::new(
391 SecurityErrorKind::CryptoFailed,
392 alloc::format!("pki: rsa sign failed: {e}"),
393 )
394 })?;
395 Ok(out)
396 }
397 CertKeyAlgo::Unknown => Err(SecurityError::new(
398 SecurityErrorKind::InvalidConfiguration,
399 "pki: cannot sign — unsupported key algo",
400 )),
401 }
402}
403
404fn verify_signature_with_cert(
405 cert_der: &[u8],
406 key_algo: CertKeyAlgo,
407 msg: &[u8],
408 sig: &[u8],
409) -> SecurityResult<()> {
410 let cert = CertificateDer::from_slice(cert_der);
411 let ee = webpki::EndEntityCert::try_from(&cert).map_err(|e| {
412 SecurityError::new(
413 SecurityErrorKind::AuthenticationFailed,
414 alloc::format!("pki: peer cert parse failed: {e:?}"),
415 )
416 })?;
417 let alg: &dyn rustls_pki_types::SignatureVerificationAlgorithm = match key_algo {
418 #[cfg(not(feature = "aws-lc"))]
419 CertKeyAlgo::EcdsaP256Sha256 => webpki::ring::ECDSA_P256_SHA256,
420 #[cfg(feature = "aws-lc")]
421 CertKeyAlgo::EcdsaP256Sha256 => webpki::aws_lc_rs::ECDSA_P256_SHA256,
422 #[cfg(not(feature = "aws-lc"))]
423 CertKeyAlgo::RsaPssSha256 => webpki::ring::RSA_PSS_2048_8192_SHA256_LEGACY_KEY,
424 #[cfg(feature = "aws-lc")]
425 CertKeyAlgo::RsaPssSha256 => webpki::aws_lc_rs::RSA_PSS_2048_8192_SHA256_LEGACY_KEY,
426 CertKeyAlgo::Unknown => {
427 return Err(SecurityError::new(
428 SecurityErrorKind::InvalidConfiguration,
429 "pki: peer cert algo unsupported",
430 ));
431 }
432 };
433 ee.verify_signature(alg, msg, sig).map_err(|e| {
434 SecurityError::new(
435 SecurityErrorKind::AuthenticationFailed,
436 alloc::format!("pki: signature verify failed: {e:?}"),
437 )
438 })
439}
440
441fn detect_peer_algo(cert_der: &[u8]) -> CertKeyAlgo {
445 crate::identity::detect_cert_algo_pub(cert_der)
446}
447
448fn der_to_pem(der: &[u8]) -> Vec<u8> {
452 use x509_cert::Certificate;
453 use x509_cert::der::{Decode, EncodePem, pem::LineEnding};
454 match Certificate::from_der(der).and_then(|c| c.to_pem(LineEnding::LF)) {
455 Ok(pem) => pem.into_bytes(),
456 Err(_) => der.to_vec(),
457 }
458}
459
460fn cid_to_der(bytes: &[u8]) -> Vec<u8> {
465 use x509_cert::Certificate;
466 use x509_cert::der::{DecodePem, Encode};
467 let bytes = match bytes.iter().rposition(|&b| b != 0) {
473 Some(i) => &bytes[..=i],
474 None => bytes,
475 };
476 if bytes.starts_with(b"-----") {
477 if let Ok(cert) = Certificate::from_pem(bytes) {
478 if let Ok(der) = cert.to_der() {
479 return der;
480 }
481 }
482 }
483 bytes.to_vec()
484}
485
486fn derive_shared_secret(raw_dh: &[u8]) -> SecurityResult<Vec<u8>> {
497 let d = digest::digest(&digest::SHA256, raw_dh);
498 Ok(d.as_ref().to_vec())
499}
500
501impl AuthenticationPlugin for PkiAuthenticationPlugin {
502 fn validate_local_identity(
503 &mut self,
504 props: &PropertyList,
505 participant_guid: [u8; 16],
506 ) -> SecurityResult<IdentityHandle> {
507 let cert = props.get(keys::IDENTITY_CERT).ok_or_else(|| {
508 SecurityError::new(
509 SecurityErrorKind::InvalidConfiguration,
510 "pki: missing dds.sec.auth.identity_certificate",
511 )
512 })?;
513 let ca = props.get(keys::IDENTITY_CA).ok_or_else(|| {
514 SecurityError::new(
515 SecurityErrorKind::InvalidConfiguration,
516 "pki: missing dds.sec.auth.identity_ca",
517 )
518 })?;
519 let cfg = IdentityConfig {
520 identity_cert_pem: cert.as_bytes().to_vec(),
521 identity_ca_pem: ca.as_bytes().to_vec(),
522 identity_key_pem: props.get(keys::IDENTITY_KEY).map(|s| s.as_bytes().to_vec()),
523 };
524 self.validate_with_config(cfg, participant_guid)
525 }
526
527 fn validate_remote_identity(
528 &mut self,
529 local: IdentityHandle,
530 _remote_participant_guid: [u8; 16],
531 remote_auth_token: &[u8],
532 ) -> SecurityResult<IdentityHandle> {
533 let parsed = self.identities.get(&local).ok_or_else(|| {
534 SecurityError::new(
535 SecurityErrorKind::BadArgument,
536 "pki: unknown local IdentityHandle",
537 )
538 })?;
539 let is_spec_descriptor =
560 zerodds_security::token::DataHolder::from_cdr_le(remote_auth_token)
561 .map(|dh| dh.class_id.starts_with("DDS:Auth:PKI-DH"))
562 .unwrap_or(false);
563 if !is_spec_descriptor {
564 parsed
565 .verify_remote_der(remote_auth_token)
566 .map_err(pki_to_security)?;
567 }
568 let handle = IdentityHandle(self.next_id());
569 Ok(handle)
570 }
571
572 fn get_identity_token(&self, local: IdentityHandle) -> SecurityResult<Vec<u8>> {
573 let parsed = self.identities.get(&local).ok_or_else(|| {
574 SecurityError::new(
575 SecurityErrorKind::BadArgument,
576 "pki: unknown local IdentityHandle",
577 )
578 })?;
579 let ca_der = parsed.trust_anchors_der.first().ok_or_else(|| {
580 SecurityError::new(SecurityErrorKind::BadArgument, "pki: no trust anchor")
581 })?;
582 let token = crate::identity_token::build_identity_token_from_der(&parsed.cert_der, ca_der)
583 .map_err(pki_to_security)?;
584 Ok(token.encode())
585 }
586
587 fn get_permissions_token(&self) -> Vec<u8> {
588 if self.local_permissions.is_empty() {
591 return Vec::new();
592 }
593 ht::DataHolder::new("DDS:Access:Permissions:1.0").to_cdr_le()
599 }
600
601 fn set_local_participant_data(&mut self, pdata: Vec<u8>) {
602 self.local_pdata = pdata;
603 }
604
605 fn set_algo_nul_terminate(&mut self, nul: bool) {
606 self.algo_nul = nul;
607 }
608
609 fn begin_handshake_request(
610 &mut self,
611 initiator: IdentityHandle,
612 _replier: IdentityHandle,
613 ) -> SecurityResult<(HandshakeHandle, HandshakeStepOutcome)> {
614 let parsed = self.identities.get(&initiator).ok_or_else(|| {
615 SecurityError::new(
616 SecurityErrorKind::BadArgument,
617 "pki: unknown initiator IdentityHandle",
618 )
619 })?;
620 let cert_der = der_to_pem(&parsed.cert_der);
624 let key_algo = parsed.key_algo;
625 let dsign_algo = self.algo_str(algo_for(key_algo)?);
626 let suite = self.preferred_kx_suite;
630 let kagree_algo = self.algo_str(Self::kagree_algo_str(suite));
631
632 let kx = KeyExchange::with_suite(suite)?;
633 let dh1 = kx.public_key().to_vec();
634 let challenge1 = random_challenge()?;
635 let permissions: Vec<u8> = self.local_permissions.clone();
639 let pdata: Vec<u8> = self.local_pdata.clone();
640
641 let bytes = ht::build_request_token(&RequestBuildInput {
642 cert_der: &cert_der,
643 permissions: &permissions,
644 pdata: &pdata,
645 dsign_algo: &dsign_algo,
646 kagree_algo: &kagree_algo,
647 dh1: &dh1,
648 challenge1: &challenge1,
649 ocsp_status: &[],
650 })?;
651
652 let hash_c1 =
653 ht::compute_hash_c(&cert_der, &permissions, &pdata, &dsign_algo, &kagree_algo);
654
655 let handle = HandshakeHandle(self.next_id());
656 self.pending_initiator.insert(
657 handle,
658 InitiatorState {
659 local: initiator,
660 kx: Some(kx),
661 dh1,
662 challenge1,
663 hash_c1,
664 permissions,
665 pdata,
666 kagree_algo,
667 dsign_algo,
668 },
669 );
670 Ok((handle, HandshakeStepOutcome::SendMessage { token: bytes }))
671 }
672
673 fn begin_handshake_reply(
674 &mut self,
675 replier: IdentityHandle,
676 _initiator: IdentityHandle,
677 request_token: &[u8],
678 ) -> SecurityResult<(HandshakeHandle, HandshakeStepOutcome)> {
679 let req = ht::parse_request_token(request_token)?;
681 let reply_kagree = self.algo_str(&req.kagree_algo);
686
687 let parsed = self.identities.get(&replier).ok_or_else(|| {
689 SecurityError::new(
690 SecurityErrorKind::BadArgument,
691 "pki: unknown replier IdentityHandle",
692 )
693 })?;
694 parsed
695 .verify_remote_der(&cid_to_der(&req.cert_der))
696 .map_err(pki_to_security)?;
697
698 self.record_challenge(replier, req.challenge1)?;
700
701 let initiator_key_algo = detect_peer_algo(&cid_to_der(&req.cert_der));
703 check_dsign_matches(&req.dsign_algo, initiator_key_algo)?;
704
705 let suite = Self::kx_suite_for_algo(&req.kagree_algo).ok_or_else(|| {
709 SecurityError::new(
710 SecurityErrorKind::AuthenticationFailed,
711 "pki: unsupported c.kagree_algo in the request",
712 )
713 })?;
714 let kx = KeyExchange::with_suite(suite)?;
715 let dh2 = kx.public_key().to_vec();
716 let challenge2 = random_challenge()?;
717
718 let parsed = self.identities.get(&replier).ok_or_else(|| {
720 SecurityError::new(SecurityErrorKind::Internal, "pki: replier identity gone")
721 })?;
722 let priv_key = parsed.private_key_pkcs8_der.clone().ok_or_else(|| {
724 SecurityError::new(
725 SecurityErrorKind::InvalidConfiguration,
726 "pki: replier has no private key configured (cannot sign)",
727 )
728 })?;
729 let replier_cert_der = der_to_pem(&parsed.cert_der);
730 let replier_dsign = self.algo_str(algo_for(parsed.key_algo)?);
731 let replier_key_algo = parsed.key_algo;
732
733 let secret_bytes = kx.derive_shared_secret(&req.dh1)?;
735 let final_secret = derive_shared_secret(&secret_bytes)?;
737
738 let permissions: Vec<u8> = self.local_permissions.clone();
742 let pdata: Vec<u8> = self.local_pdata.clone();
743 let hash_c2 = ht::compute_hash_c(
744 &replier_cert_der,
745 &permissions,
746 &pdata,
747 &replier_dsign,
748 &reply_kagree,
749 );
750
751 let to_sign = ht::reply_signing_bytes(
754 &hash_c2,
755 &challenge2,
756 &dh2,
757 &req.challenge1,
758 &req.dh1,
759 &req.hash_c1,
760 );
761 let signature = sign_with(replier_key_algo, &priv_key, &to_sign)?;
762
763 let reply_bytes = ht::build_reply_token(&ReplyBuildInput {
765 cert_der: &replier_cert_der,
766 permissions: &permissions,
767 pdata: &pdata,
768 dsign_algo: &replier_dsign,
769 kagree_algo: &reply_kagree,
770 dh2: &dh2,
771 challenge2: &challenge2,
772 hash_c1: &req.hash_c1,
773 dh1: &req.dh1,
774 challenge1: &req.challenge1,
775 ocsp_status: &[],
776 signature: &signature,
777 })?;
778
779 let handle = HandshakeHandle(self.next_id());
782 let secret_handle = self.store_secret(handle, final_secret, req.challenge1, challenge2);
783 self.handshake_to_secret.insert(handle, secret_handle);
784 self.pending_replier.insert(
785 handle,
786 ReplierState {
787 local: replier,
788 challenge1: req.challenge1,
789 challenge2,
790 dh1: req.dh1,
791 dh2,
792 hash_c1: req.hash_c1,
793 hash_c2,
794 secret_handle,
795 initiator_cert_der: req.cert_der,
796 initiator_key_algo,
797 },
798 );
799
800 Ok((
801 handle,
802 HandshakeStepOutcome::SendMessage { token: reply_bytes },
803 ))
804 }
805
806 fn process_handshake(
807 &mut self,
808 handshake: HandshakeHandle,
809 token: &[u8],
810 ) -> SecurityResult<HandshakeStepOutcome> {
811 if self.pending_initiator.contains_key(&handshake) {
813 return self.process_reply_on_initiator(handshake, token);
814 }
815 if self.pending_replier.contains_key(&handshake) {
817 return self.process_final_on_replier(handshake, token);
818 }
819 Err(SecurityError::new(
820 SecurityErrorKind::BadArgument,
821 "pki: unknown HandshakeHandle",
822 ))
823 }
824
825 fn shared_secret(&self, handshake: HandshakeHandle) -> SecurityResult<SharedSecretHandle> {
826 self.handshake_to_secret
827 .get(&handshake)
828 .copied()
829 .ok_or_else(|| {
830 SecurityError::new(
831 SecurityErrorKind::BadArgument,
832 "pki: handshake handle unknown or not yet completed",
833 )
834 })
835 }
836
837 fn plugin_class_id(&self) -> &str {
838 "DDS:Auth:PKI-DH:1.2"
839 }
840}
841
842impl PkiAuthenticationPlugin {
843 fn process_reply_on_initiator(
844 &mut self,
845 handshake: HandshakeHandle,
846 token: &[u8],
847 ) -> SecurityResult<HandshakeStepOutcome> {
848 let reply = ht::parse_reply_token(token)?;
849
850 let st = self.pending_initiator.remove(&handshake).ok_or_else(|| {
851 SecurityError::new(SecurityErrorKind::BadArgument, "pki: initiator state gone")
852 })?;
853
854 if let Some(reply_hash_c1) = reply.hash_c1 {
863 if !ct_eq(&reply_hash_c1, &st.hash_c1) {
864 return Err(SecurityError::new(
865 SecurityErrorKind::AuthenticationFailed,
866 "reply: hash_c1 echo mismatch (cert-bind broken)",
867 ));
868 }
869 }
870 if let Some(reply_dh1) = &reply.dh1 {
871 if !ct_eq(reply_dh1, &st.dh1) {
872 return Err(SecurityError::new(
873 SecurityErrorKind::AuthenticationFailed,
874 "reply: dh1 echo mismatch",
875 ));
876 }
877 }
878 if !ct_eq(&reply.challenge1, &st.challenge1) {
879 return Err(SecurityError::new(
880 SecurityErrorKind::AuthenticationFailed,
881 "reply: challenge1 echo mismatch",
882 ));
883 }
884 if reply.kagree_algo.trim_end_matches('\0') != st.kagree_algo.trim_end_matches('\0') {
889 return Err(SecurityError::new(
890 SecurityErrorKind::AuthenticationFailed,
891 "reply: kagree_algo mismatch",
892 ));
893 }
894
895 let (priv_key, initiator_key_algo) = {
899 let parsed = self.identities.get(&st.local).ok_or_else(|| {
900 SecurityError::new(SecurityErrorKind::Internal, "pki: initiator identity gone")
901 })?;
902 parsed
903 .verify_remote_der(&cid_to_der(&reply.cert_der))
904 .map_err(pki_to_security)?;
905 let pk = parsed.private_key_pkcs8_der.clone().ok_or_else(|| {
906 SecurityError::new(
907 SecurityErrorKind::InvalidConfiguration,
908 "pki: initiator has no private key (final-sign not possible)",
909 )
910 })?;
911 (pk, parsed.key_algo)
912 };
913 let replier_key_algo = detect_peer_algo(&cid_to_der(&reply.cert_der));
914 check_dsign_matches(&reply.dsign_algo, replier_key_algo)?;
915
916 let to_verify = ht::reply_signing_bytes(
922 &reply.hash_c2,
923 &reply.challenge2,
924 &reply.dh2,
925 &reply.challenge1,
926 &st.dh1,
927 &st.hash_c1,
928 );
929 verify_signature_with_cert(
930 &cid_to_der(&reply.cert_der),
931 replier_key_algo,
932 &to_verify,
933 &reply.signature,
934 )?;
935
936 let kx = st.kx.ok_or_else(|| {
938 SecurityError::new(SecurityErrorKind::Internal, "pki: ephemeral kx gone")
939 })?;
940 let raw = kx.derive_shared_secret(&reply.dh2)?;
941 let final_secret = derive_shared_secret(&raw)?;
942
943 let secret_handle =
945 self.store_secret(handshake, final_secret, st.challenge1, reply.challenge2);
946 self.handshake_to_secret.insert(handshake, secret_handle);
947
948 let to_sign = ht::final_signing_bytes(
951 &st.hash_c1,
952 &reply.challenge1,
953 &st.dh1,
954 &reply.challenge2,
955 &reply.dh2,
956 &reply.hash_c2,
957 );
958 let signature = sign_with(initiator_key_algo, &priv_key, &to_sign)?;
959 let final_token = ht::build_final_token(&FinalBuildInput {
963 hash_c1: &st.hash_c1,
964 hash_c2: &reply.hash_c2,
965 dh1: &st.dh1,
966 dh2: &reply.dh2,
967 challenge1: &reply.challenge1,
968 challenge2: &reply.challenge2,
969 ocsp_status: &[],
970 signature: &signature,
971 })?;
972
973 Ok(HandshakeStepOutcome::SendMessage { token: final_token })
979 }
980
981 fn process_final_on_replier(
982 &mut self,
983 handshake: HandshakeHandle,
984 token: &[u8],
985 ) -> SecurityResult<HandshakeStepOutcome> {
986 let final_tok = ht::parse_final_token(token)?;
987 let st = self.pending_replier.remove(&handshake).ok_or_else(|| {
988 SecurityError::new(SecurityErrorKind::BadArgument, "pki: replier state gone")
989 })?;
990
991 if !ct_eq(&final_tok.hash_c1, &st.hash_c1)
993 || !ct_eq(&final_tok.hash_c2, &st.hash_c2)
994 || !ct_eq(&final_tok.dh1, &st.dh1)
995 || !ct_eq(&final_tok.dh2, &st.dh2)
996 || !ct_eq(&final_tok.challenge1, &st.challenge1)
997 || !ct_eq(&final_tok.challenge2, &st.challenge2)
998 {
999 return Err(SecurityError::new(
1000 SecurityErrorKind::AuthenticationFailed,
1001 "final: echo mismatch",
1002 ));
1003 }
1004
1005 let to_verify = ht::final_signing_bytes(
1008 &st.hash_c1,
1009 &st.challenge1,
1010 &st.dh1,
1011 &st.challenge2,
1012 &st.dh2,
1013 &st.hash_c2,
1014 );
1015 verify_signature_with_cert(
1016 &cid_to_der(&st.initiator_cert_der),
1017 st.initiator_key_algo,
1018 &to_verify,
1019 &final_tok.signature,
1020 )?;
1021
1022 let _ = st.local;
1026 Ok(HandshakeStepOutcome::Complete {
1027 secret: st.secret_handle,
1028 })
1029 }
1030}
1031
1032#[cfg(test)]
1033#[allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
1034mod tests {
1035 use super::*;
1036 use zerodds_security::properties::Property;
1037
1038 fn make_signed_cert_ca_key() -> (Vec<u8>, Vec<u8>, Vec<u8>) {
1041 use rcgen::{CertificateParams, KeyPair};
1042
1043 let mut ca_params = CertificateParams::new(vec!["ZeroDDS Test CA".into()]).unwrap();
1044 ca_params.is_ca = rcgen::IsCa::Ca(rcgen::BasicConstraints::Unconstrained);
1045 let ca_key = KeyPair::generate().unwrap();
1046 let ca_cert = ca_params.self_signed(&ca_key).unwrap();
1047 let ca_pem = ca_cert.pem();
1048
1049 let mut ee_params = CertificateParams::new(vec!["zerodds-node".into()]).unwrap();
1050 ee_params.is_ca = rcgen::IsCa::NoCa;
1051 let ee_key = KeyPair::generate().unwrap();
1052 let ee_cert = ee_params.signed_by(&ee_key, &ca_cert, &ca_key).unwrap();
1053 let ee_pem = ee_cert.pem();
1054 let ee_key_pem = ee_key.serialize_pem();
1055
1056 (
1057 ee_pem.into_bytes(),
1058 ca_pem.into_bytes(),
1059 ee_key_pem.into_bytes(),
1060 )
1061 }
1062
1063 fn make_cert_with_wrong_ca() -> (Vec<u8>, Vec<u8>, Vec<u8>) {
1064 use rcgen::{CertificateParams, KeyPair};
1065
1066 let mut trusted_ca_params = CertificateParams::new(vec!["Trusted CA".into()]).unwrap();
1067 trusted_ca_params.is_ca = rcgen::IsCa::Ca(rcgen::BasicConstraints::Unconstrained);
1068 let trusted_ca_key = KeyPair::generate().unwrap();
1069 let trusted_ca_cert = trusted_ca_params.self_signed(&trusted_ca_key).unwrap();
1070
1071 let mut rogue_ca_params = CertificateParams::new(vec!["Rogue CA".into()]).unwrap();
1072 rogue_ca_params.is_ca = rcgen::IsCa::Ca(rcgen::BasicConstraints::Unconstrained);
1073 let rogue_ca_key = KeyPair::generate().unwrap();
1074 let rogue_ca_cert = rogue_ca_params.self_signed(&rogue_ca_key).unwrap();
1075
1076 let mut ee_params = CertificateParams::new(vec!["impersonator".into()]).unwrap();
1077 ee_params.is_ca = rcgen::IsCa::NoCa;
1078 let ee_key = KeyPair::generate().unwrap();
1079 let ee_cert = ee_params
1080 .signed_by(&ee_key, &rogue_ca_cert, &rogue_ca_key)
1081 .unwrap();
1082
1083 (
1084 ee_cert.pem().into_bytes(),
1085 trusted_ca_cert.pem().into_bytes(),
1086 ee_key.serialize_pem().into_bytes(),
1087 )
1088 }
1089
1090 fn alice_bob() -> (
1091 PkiAuthenticationPlugin,
1092 PkiAuthenticationPlugin,
1093 IdentityHandle,
1094 IdentityHandle,
1095 IdentityHandle,
1096 IdentityHandle,
1097 ) {
1098 let (a_cert, ca, a_key) = make_signed_cert_ca_key();
1099 let (b_cert, _ca2, b_key) = make_signed_cert_ca_key();
1100 let _ = (b_cert, b_key);
1106
1107 use rcgen::{CertificateParams, KeyPair};
1109 let mut ca_params = CertificateParams::new(vec!["Common CA".into()]).unwrap();
1110 ca_params.is_ca = rcgen::IsCa::Ca(rcgen::BasicConstraints::Unconstrained);
1111 let ca_key = KeyPair::generate().unwrap();
1112 let ca_cert = ca_params.self_signed(&ca_key).unwrap();
1113 let ca_pem = ca_cert.pem().into_bytes();
1114
1115 let mut alice_params = CertificateParams::new(vec!["alice".into()]).unwrap();
1116 alice_params.is_ca = rcgen::IsCa::NoCa;
1117 let alice_key_pair = KeyPair::generate().unwrap();
1118 let alice_cert = alice_params
1119 .signed_by(&alice_key_pair, &ca_cert, &ca_key)
1120 .unwrap();
1121 let alice_cert_pem = alice_cert.pem().into_bytes();
1122 let alice_key_pem = alice_key_pair.serialize_pem().into_bytes();
1123
1124 let mut bob_params = CertificateParams::new(vec!["bob".into()]).unwrap();
1125 bob_params.is_ca = rcgen::IsCa::NoCa;
1126 let bob_key_pair = KeyPair::generate().unwrap();
1127 let bob_cert = bob_params
1128 .signed_by(&bob_key_pair, &ca_cert, &ca_key)
1129 .unwrap();
1130 let bob_cert_pem = bob_cert.pem().into_bytes();
1131 let bob_key_pem = bob_key_pair.serialize_pem().into_bytes();
1132
1133 let mut alice = PkiAuthenticationPlugin::new();
1134 let mut bob = PkiAuthenticationPlugin::new();
1135 let alice_h = alice
1136 .validate_with_config(
1137 IdentityConfig {
1138 identity_cert_pem: alice_cert_pem.clone(),
1139 identity_ca_pem: ca_pem.clone(),
1140 identity_key_pem: Some(alice_key_pem),
1141 },
1142 [0xAA; 16],
1143 )
1144 .unwrap();
1145 let alice_remote_for_bob = alice
1146 .validate_remote_identity(alice_h, [0xBB; 16], &cert_der_from_pem(&bob_cert_pem))
1147 .unwrap();
1148 let bob_h = bob
1149 .validate_with_config(
1150 IdentityConfig {
1151 identity_cert_pem: bob_cert_pem.clone(),
1152 identity_ca_pem: ca_pem,
1153 identity_key_pem: Some(bob_key_pem),
1154 },
1155 [0xBB; 16],
1156 )
1157 .unwrap();
1158 let bob_remote_for_alice = bob
1159 .validate_remote_identity(bob_h, [0xAA; 16], &cert_der_from_pem(&alice_cert_pem))
1160 .unwrap();
1161 let _ = (a_cert, ca, a_key);
1162 (
1163 alice,
1164 bob,
1165 alice_h,
1166 alice_remote_for_bob,
1167 bob_h,
1168 bob_remote_for_alice,
1169 )
1170 }
1171
1172 fn cert_der_from_pem(pem: &[u8]) -> Vec<u8> {
1173 use rustls_pki_types::CertificateDer;
1174 use rustls_pki_types::pem::PemObject;
1175 CertificateDer::pem_slice_iter(pem)
1176 .next()
1177 .unwrap()
1178 .unwrap()
1179 .as_ref()
1180 .to_vec()
1181 }
1182
1183 #[test]
1184 fn plugin_class_id_matches_spec() {
1185 let p = PkiAuthenticationPlugin::new();
1186 assert_eq!(p.plugin_class_id(), "DDS:Auth:PKI-DH:1.2");
1187 }
1188
1189 #[test]
1190 fn validate_local_identity_accepts_ca_signed_cert() {
1191 let (cert_pem, ca_pem, key_pem) = make_signed_cert_ca_key();
1192 let mut plugin = PkiAuthenticationPlugin::new();
1193 let cfg = IdentityConfig {
1194 identity_cert_pem: cert_pem,
1195 identity_ca_pem: ca_pem,
1196 identity_key_pem: Some(key_pem),
1197 };
1198 let handle = plugin
1199 .validate_with_config(cfg, [0xAA; 16])
1200 .expect("signed cert must validate");
1201 assert_eq!(handle, IdentityHandle(1));
1202 }
1203
1204 #[test]
1205 fn validate_local_identity_rejects_wrong_ca() {
1206 let (cert_pem, trusted_ca_pem, key_pem) = make_cert_with_wrong_ca();
1207 let mut plugin = PkiAuthenticationPlugin::new();
1208 let cfg = IdentityConfig {
1209 identity_cert_pem: cert_pem,
1210 identity_ca_pem: trusted_ca_pem,
1211 identity_key_pem: Some(key_pem),
1212 };
1213 let err = plugin
1214 .validate_with_config(cfg, [0xAA; 16])
1215 .expect_err("rogue-CA cert must not validate");
1216 assert_eq!(err.kind, SecurityErrorKind::AuthenticationFailed);
1217 }
1218
1219 #[test]
1220 fn validate_local_identity_rejects_empty_trust_anchors() {
1221 let (cert_pem, _, _) = make_signed_cert_ca_key();
1222 let mut plugin = PkiAuthenticationPlugin::new();
1223 let cfg = IdentityConfig {
1224 identity_cert_pem: cert_pem,
1225 identity_ca_pem: b"".to_vec(),
1226 identity_key_pem: None,
1227 };
1228 let err = plugin.validate_with_config(cfg, [0xAA; 16]).unwrap_err();
1229 assert_eq!(err.kind, SecurityErrorKind::InvalidConfiguration);
1230 }
1231
1232 #[test]
1233 fn validate_local_identity_via_property_list() {
1234 let (cert_pem, ca_pem, key_pem) = make_signed_cert_ca_key();
1235 let mut plugin = PkiAuthenticationPlugin::new();
1236 let cert_str = std::str::from_utf8(&cert_pem).unwrap().to_owned();
1237 let ca_str = std::str::from_utf8(&ca_pem).unwrap().to_owned();
1238 let key_str = std::str::from_utf8(&key_pem).unwrap().to_owned();
1239 let props = PropertyList::new()
1240 .with(Property::local(
1241 "dds.sec.auth.identity_certificate",
1242 cert_str,
1243 ))
1244 .with(Property::local("dds.sec.auth.identity_ca", ca_str))
1245 .with(Property::local("dds.sec.auth.private_key", key_str));
1246 let handle = plugin
1247 .validate_local_identity(&props, [0xAA; 16])
1248 .expect("validate via props");
1249 assert!(handle.0 >= 1);
1250 }
1251
1252 #[test]
1257 fn full_three_round_handshake_alice_bob() {
1258 let (mut alice, mut bob, alice_h, alice_remote_bob, bob_h, bob_remote_alice) = alice_bob();
1259
1260 let (alice_hs, out1) = alice
1262 .begin_handshake_request(alice_h, alice_remote_bob)
1263 .unwrap();
1264 let req_token = match out1 {
1265 HandshakeStepOutcome::SendMessage { token } => token,
1266 _ => panic!("expected SendMessage"),
1267 };
1268 assert!(req_token.len() > 100, "request token contains cert + DH");
1269
1270 let (bob_hs, out2) = bob
1272 .begin_handshake_reply(bob_h, bob_remote_alice, &req_token)
1273 .unwrap();
1274 let reply_token = match out2 {
1275 HandshakeStepOutcome::SendMessage { token } => token,
1276 _ => panic!("expected SendMessage"),
1277 };
1278
1279 let out3 = alice.process_handshake(alice_hs, &reply_token).unwrap();
1281 let final_token = match out3 {
1282 HandshakeStepOutcome::SendMessage { token } => token,
1283 _ => panic!("alice expected to send final token"),
1284 };
1285
1286 let out4 = bob.process_handshake(bob_hs, &final_token).unwrap();
1288 let bob_secret = match out4 {
1289 HandshakeStepOutcome::Complete { secret } => secret,
1290 _ => panic!("expected Complete"),
1291 };
1292
1293 let alice_secret = alice.shared_secret(alice_hs).unwrap();
1294 let a_bytes = alice.secret_bytes(alice_secret).unwrap();
1295 let b_bytes = bob.secret_bytes(bob_secret).unwrap();
1296 assert_eq!(a_bytes.len(), 32);
1297 assert_eq!(
1298 a_bytes, b_bytes,
1299 "alice + bob must derive an identical secret"
1300 );
1301 }
1302
1303 #[test]
1304 fn request_token_has_spec_class_id_and_properties() {
1305 let (mut alice, _bob, alice_h, alice_remote_bob, _, _) = alice_bob();
1306 let (_, out) = alice
1307 .begin_handshake_request(alice_h, alice_remote_bob)
1308 .unwrap();
1309 let token = match out {
1310 HandshakeStepOutcome::SendMessage { token } => token,
1311 _ => panic!(),
1312 };
1313 let parsed = ht::DataHolder::from_cdr_le(&token).unwrap();
1314 assert_eq!(parsed.class_id, "DDS:Auth:PKI-DH:1.0+Req");
1315 assert!(parsed.binary_property("c.dsign_algo").is_some());
1317 assert!(parsed.binary_property("c.kagree_algo").is_some());
1318 for k in ["c.id", "c.perm", "c.pdata", "hash_c1", "dh1", "challenge1"] {
1321 assert!(
1322 parsed.binary_property(k).is_some(),
1323 "missing binary prop: {k}"
1324 );
1325 }
1326 }
1327
1328 #[test]
1329 fn cert_bind_replier_modifies_initiator_cert_in_reply_rejected() {
1330 let (mut alice, mut bob, alice_h, alice_remote_bob, bob_h, bob_remote_alice) = alice_bob();
1331 let (alice_hs, out1) = alice
1332 .begin_handshake_request(alice_h, alice_remote_bob)
1333 .unwrap();
1334 let req = match out1 {
1335 HandshakeStepOutcome::SendMessage { token } => token,
1336 _ => panic!(),
1337 };
1338 let (_bob_hs, out2) = bob
1339 .begin_handshake_reply(bob_h, bob_remote_alice, &req)
1340 .unwrap();
1341 let mut reply_token = match out2 {
1342 HandshakeStepOutcome::SendMessage { token } => token,
1343 _ => panic!(),
1344 };
1345
1346 let mut h = ht::DataHolder::from_cdr_le(&reply_token).unwrap();
1348 let mut hash_c1 = h.binary_property("hash_c1").unwrap().to_vec();
1349 hash_c1[0] ^= 0x01;
1350 h.set_binary_property("hash_c1", hash_c1);
1351 reply_token = h.to_cdr_le();
1352
1353 let err = alice.process_handshake(alice_hs, &reply_token).unwrap_err();
1354 assert_eq!(err.kind, SecurityErrorKind::AuthenticationFailed);
1355 }
1356
1357 #[test]
1358 fn signature_tamper_rejected_by_initiator() {
1359 let (mut alice, mut bob, alice_h, alice_remote_bob, bob_h, bob_remote_alice) = alice_bob();
1360 let (alice_hs, out1) = alice
1361 .begin_handshake_request(alice_h, alice_remote_bob)
1362 .unwrap();
1363 let req = match out1 {
1364 HandshakeStepOutcome::SendMessage { token } => token,
1365 _ => panic!(),
1366 };
1367 let (_, out2) = bob
1368 .begin_handshake_reply(bob_h, bob_remote_alice, &req)
1369 .unwrap();
1370 let mut reply = match out2 {
1371 HandshakeStepOutcome::SendMessage { token } => token,
1372 _ => panic!(),
1373 };
1374
1375 let mut h = ht::DataHolder::from_cdr_le(&reply).unwrap();
1376 let mut sig = h.binary_property("signature").unwrap().to_vec();
1377 sig[0] ^= 0x01;
1378 h.set_binary_property("signature", sig);
1379 reply = h.to_cdr_le();
1380
1381 let err = alice.process_handshake(alice_hs, &reply).unwrap_err();
1382 assert_eq!(err.kind, SecurityErrorKind::AuthenticationFailed);
1383 }
1384
1385 #[test]
1386 fn dh_tamper_in_reply_breaks_final_signature() {
1387 let (mut alice, mut bob, alice_h, alice_remote_bob, bob_h, bob_remote_alice) = alice_bob();
1388 let (alice_hs, out1) = alice
1389 .begin_handshake_request(alice_h, alice_remote_bob)
1390 .unwrap();
1391 let req = match out1 {
1392 HandshakeStepOutcome::SendMessage { token } => token,
1393 _ => panic!(),
1394 };
1395 let (bob_hs, out2) = bob
1396 .begin_handshake_reply(bob_h, bob_remote_alice, &req)
1397 .unwrap();
1398 let mut reply = match out2 {
1399 HandshakeStepOutcome::SendMessage { token } => token,
1400 _ => panic!(),
1401 };
1402
1403 let mut h = ht::DataHolder::from_cdr_le(&reply).unwrap();
1407 let mut dh2 = h.binary_property("dh2").unwrap().to_vec();
1408 dh2[0] ^= 0x01;
1409 h.set_binary_property("dh2", dh2);
1410 reply = h.to_cdr_le();
1411
1412 let err = alice.process_handshake(alice_hs, &reply).unwrap_err();
1413 assert_eq!(err.kind, SecurityErrorKind::AuthenticationFailed);
1414 let _ = bob_hs;
1415 }
1416
1417 #[test]
1418 fn wrong_ca_initiator_rejected_by_replier() {
1419 let (rogue_cert, _trusted_ca, rogue_key) = make_cert_with_wrong_ca();
1420 use rcgen::{CertificateParams, KeyPair};
1433 let mut alice_ca_params = CertificateParams::new(vec!["AliceCA".into()]).unwrap();
1435 alice_ca_params.is_ca = rcgen::IsCa::Ca(rcgen::BasicConstraints::Unconstrained);
1436 let alice_ca_key = KeyPair::generate().unwrap();
1437 let alice_ca_cert = alice_ca_params.self_signed(&alice_ca_key).unwrap();
1438 let alice_ca_pem = alice_ca_cert.pem();
1439
1440 let mut alice_params = CertificateParams::new(vec!["alice".into()]).unwrap();
1441 alice_params.is_ca = rcgen::IsCa::NoCa;
1442 let alice_key = KeyPair::generate().unwrap();
1443 let alice_cert = alice_params
1444 .signed_by(&alice_key, &alice_ca_cert, &alice_ca_key)
1445 .unwrap();
1446 let alice_cert_pem = alice_cert.pem().into_bytes();
1447 let alice_key_pem = alice_key.serialize_pem().into_bytes();
1448
1449 let mut bob_ca_params = CertificateParams::new(vec!["BobCA".into()]).unwrap();
1451 bob_ca_params.is_ca = rcgen::IsCa::Ca(rcgen::BasicConstraints::Unconstrained);
1452 let bob_ca_key = KeyPair::generate().unwrap();
1453 let bob_ca_cert = bob_ca_params.self_signed(&bob_ca_key).unwrap();
1454 let bob_ca_pem = bob_ca_cert.pem();
1455 let mut bob_params = CertificateParams::new(vec!["bob".into()]).unwrap();
1456 bob_params.is_ca = rcgen::IsCa::NoCa;
1457 let bob_key = KeyPair::generate().unwrap();
1458 let bob_cert = bob_params
1459 .signed_by(&bob_key, &bob_ca_cert, &bob_ca_key)
1460 .unwrap();
1461 let bob_cert_pem = bob_cert.pem().into_bytes();
1462 let bob_key_pem = bob_key.serialize_pem().into_bytes();
1463
1464 let mut alice = PkiAuthenticationPlugin::new();
1465 let mut bob = PkiAuthenticationPlugin::new();
1466 let alice_h = alice
1467 .validate_with_config(
1468 IdentityConfig {
1469 identity_cert_pem: alice_cert_pem.clone(),
1470 identity_ca_pem: alice_ca_pem.into_bytes(),
1471 identity_key_pem: Some(alice_key_pem),
1472 },
1473 [0xAA; 16],
1474 )
1475 .unwrap();
1476 let bob_h = bob
1477 .validate_with_config(
1478 IdentityConfig {
1479 identity_cert_pem: bob_cert_pem.clone(),
1480 identity_ca_pem: bob_ca_pem.into_bytes(),
1481 identity_key_pem: Some(bob_key_pem),
1482 },
1483 [0xBB; 16],
1484 )
1485 .unwrap();
1486
1487 let (_alice_hs, out1) = alice
1493 .begin_handshake_request(alice_h, IdentityHandle(99))
1494 .unwrap();
1495 let req = match out1 {
1496 HandshakeStepOutcome::SendMessage { token } => token,
1497 _ => panic!(),
1498 };
1499 let err = bob
1501 .begin_handshake_reply(bob_h, IdentityHandle(99), &req)
1502 .unwrap_err();
1503 assert_eq!(err.kind, SecurityErrorKind::AuthenticationFailed);
1504 let _ = (rogue_cert, rogue_key);
1505 }
1506
1507 #[test]
1508 fn replay_initiator_request_rejected_second_time() {
1509 let (mut alice, mut bob, alice_h, alice_remote_bob, bob_h, bob_remote_alice) = alice_bob();
1510 let (_alice_hs, out1) = alice
1511 .begin_handshake_request(alice_h, alice_remote_bob)
1512 .unwrap();
1513 let req = match out1 {
1514 HandshakeStepOutcome::SendMessage { token } => token,
1515 _ => panic!(),
1516 };
1517 let _ = bob
1519 .begin_handshake_reply(bob_h, bob_remote_alice, &req)
1520 .unwrap();
1521 let err = bob
1523 .begin_handshake_reply(bob_h, bob_remote_alice, &req)
1524 .unwrap_err();
1525 assert_eq!(err.kind, SecurityErrorKind::AuthenticationFailed);
1526 }
1527
1528 #[test]
1529 fn truncated_request_token_rejected() {
1530 let (_alice, mut bob, _alice_h, _, bob_h, bob_remote_alice) = alice_bob();
1531 let err = bob
1532 .begin_handshake_reply(bob_h, bob_remote_alice, &[0u8, 1u8, 2u8, 3u8, 4u8])
1533 .unwrap_err();
1534 assert_eq!(err.kind, SecurityErrorKind::BadArgument);
1535 }
1536
1537 #[test]
1538 fn hash_c1_mismatch_in_request_rejected() {
1539 let (mut alice, mut bob, alice_h, alice_remote_bob, bob_h, bob_remote_alice) = alice_bob();
1540 let (_, out1) = alice
1541 .begin_handshake_request(alice_h, alice_remote_bob)
1542 .unwrap();
1543 let req = match out1 {
1544 HandshakeStepOutcome::SendMessage { token } => token,
1545 _ => panic!(),
1546 };
1547 let mut h = ht::DataHolder::from_cdr_le(&req).unwrap();
1549 let mut hc = h.binary_property("hash_c1").unwrap().to_vec();
1550 hc[5] ^= 0xFF;
1551 h.set_binary_property("hash_c1", hc);
1552 let bad = h.to_cdr_le();
1553 let err = bob
1554 .begin_handshake_reply(bob_h, bob_remote_alice, &bad)
1555 .unwrap_err();
1556 assert_eq!(err.kind, SecurityErrorKind::AuthenticationFailed);
1557 }
1558
1559 #[test]
1560 fn cross_algorithm_dsign_mismatch_rejected() {
1561 let (mut alice, mut bob, alice_h, alice_remote_bob, bob_h, bob_remote_alice) = alice_bob();
1562 let (_alice_hs, out1) = alice
1563 .begin_handshake_request(alice_h, alice_remote_bob)
1564 .unwrap();
1565 let mut req = match out1 {
1566 HandshakeStepOutcome::SendMessage { token } => token,
1567 _ => panic!(),
1568 };
1569 let mut h = ht::DataHolder::from_cdr_le(&req).unwrap();
1575 h.set_binary_property("c.dsign_algo", b"RSASSA-PSS-SHA256".to_vec());
1582 let cert_der = h.binary_property("c.id").unwrap().to_vec();
1583 let perm = h.binary_property("c.perm").unwrap().to_vec();
1584 let pdata = h.binary_property("c.pdata").unwrap().to_vec();
1585 let mut kagree_bytes = h.binary_property("c.kagree_algo").unwrap().to_vec();
1586 if kagree_bytes.last() == Some(&0) {
1587 kagree_bytes.pop();
1588 }
1589 let kagree = String::from_utf8(kagree_bytes).unwrap();
1590 let new_hash = ht::compute_hash_c(&cert_der, &perm, &pdata, "RSASSA-PSS-SHA256", &kagree);
1591 h.set_binary_property("hash_c1", new_hash.to_vec());
1592 req = h.to_cdr_le();
1593 let err = bob
1594 .begin_handshake_reply(bob_h, bob_remote_alice, &req)
1595 .unwrap_err();
1596 assert_eq!(err.kind, SecurityErrorKind::InvalidConfiguration);
1597 }
1598
1599 #[test]
1600 fn extra_unknown_properties_in_reply_accepted_forward_compat() {
1601 let (mut alice, mut bob, alice_h, alice_remote_bob, bob_h, bob_remote_alice) = alice_bob();
1602 let (alice_hs, out1) = alice
1603 .begin_handshake_request(alice_h, alice_remote_bob)
1604 .unwrap();
1605 let req = match out1 {
1606 HandshakeStepOutcome::SendMessage { token } => token,
1607 _ => panic!(),
1608 };
1609 let (_, out2) = bob
1610 .begin_handshake_reply(bob_h, bob_remote_alice, &req)
1611 .unwrap();
1612 let reply = match out2 {
1613 HandshakeStepOutcome::SendMessage { token } => token,
1614 _ => panic!(),
1615 };
1616 let mut h = ht::DataHolder::from_cdr_le(&reply).unwrap();
1618 h.set_property("zerodds.future.feature", "yes");
1619 h.set_binary_property("zerodds.future.opaque", alloc::vec![0xFF; 8]);
1620 let new_reply = h.to_cdr_le();
1623 let res = alice.process_handshake(alice_hs, &new_reply);
1624 assert!(res.is_ok(), "forward-compat extra props must be accepted");
1625 }
1626
1627 #[test]
1628 fn empty_permissions_accepted_in_phase3_mvp() {
1629 let (mut alice, mut bob, alice_h, alice_remote_bob, bob_h, bob_remote_alice) = alice_bob();
1630 let (alice_hs, out1) = alice
1631 .begin_handshake_request(alice_h, alice_remote_bob)
1632 .unwrap();
1633 let req = match out1 {
1634 HandshakeStepOutcome::SendMessage { token } => token,
1635 _ => panic!(),
1636 };
1637 let h = ht::DataHolder::from_cdr_le(&req).unwrap();
1640 assert_eq!(h.binary_property("c.perm").unwrap().len(), 0);
1641 let _ = bob
1642 .begin_handshake_reply(bob_h, bob_remote_alice, &req)
1643 .unwrap();
1644 let _ = alice_hs;
1645 }
1646
1647 #[test]
1648 fn shared_secret_returns_bad_argument_for_unknown_handle() {
1649 let plugin = PkiAuthenticationPlugin::new();
1650 let err = plugin.shared_secret(HandshakeHandle(42)).unwrap_err();
1651 assert_eq!(err.kind, SecurityErrorKind::BadArgument);
1652 }
1653
1654 #[test]
1655 fn permissions_token_announces_spec_class_id_when_permissions_set() {
1656 let mut p = PkiAuthenticationPlugin::new();
1657 assert!(
1661 p.get_permissions_token().is_empty(),
1662 "without permissions no PermissionsToken may be announced"
1663 );
1664 p.set_local_permissions(vec![0xDE, 0xAD, 0xBE, 0xEF]);
1668 let token = p.get_permissions_token();
1669 assert!(
1670 !token.is_empty(),
1671 "PermissionsToken missing despite set permissions"
1672 );
1673 let parsed = ht::DataHolder::from_cdr_le(&token).unwrap();
1674 assert_eq!(parsed.class_id, "DDS:Access:Permissions:1.0");
1675 assert!(
1676 parsed.properties.is_empty(),
1677 "PermissionsToken announce carries no properties (cyclone mirror)"
1678 );
1679 }
1680
1681 #[test]
1682 fn validate_remote_identity_accepts_trusted_cert_der() {
1683 let (cert_pem, ca_pem, key_pem) = make_signed_cert_ca_key();
1684 let mut plugin = PkiAuthenticationPlugin::new();
1685 let local = plugin
1686 .validate_with_config(
1687 IdentityConfig {
1688 identity_cert_pem: cert_pem.clone(),
1689 identity_ca_pem: ca_pem,
1690 identity_key_pem: Some(key_pem),
1691 },
1692 [0xAA; 16],
1693 )
1694 .unwrap();
1695
1696 let remote_der = cert_der_from_pem(&cert_pem);
1697 let remote = plugin
1698 .validate_remote_identity(local, [0xBB; 16], &remote_der)
1699 .expect("trusted remote must be accepted");
1700 assert_ne!(remote, local);
1701 }
1702
1703 #[test]
1708 fn get_identity_token_returns_decodable_spec_descriptor() {
1709 let (cert_pem, ca_pem, key_pem) = make_signed_cert_ca_key();
1710 let mut plugin = PkiAuthenticationPlugin::new();
1711 let local = plugin
1712 .validate_with_config(
1713 IdentityConfig {
1714 identity_cert_pem: cert_pem,
1715 identity_ca_pem: ca_pem,
1716 identity_key_pem: Some(key_pem),
1717 },
1718 [0xAA; 16],
1719 )
1720 .unwrap();
1721 let token = plugin.get_identity_token(local).expect("identity token");
1722 assert!(!token.is_empty(), "no empty default token anymore");
1723 let decoded = crate::identity_token::IdentityToken::decode(&token)
1724 .expect("must be decodable as a spec descriptor");
1725 assert!(!decoded.cert_sn.is_empty(), "cert subject in the token");
1726 assert!(!decoded.ca_sn.is_empty(), "ca subject in the token");
1727 }
1728
1729 #[test]
1730 fn validate_remote_identity_accepts_spec_descriptor_deferring_cert_check() {
1731 let (cert_pem, ca_pem, key_pem) = make_signed_cert_ca_key();
1732 let mut plugin = PkiAuthenticationPlugin::new();
1733 let local = plugin
1734 .validate_with_config(
1735 IdentityConfig {
1736 identity_cert_pem: cert_pem.clone(),
1737 identity_ca_pem: ca_pem.clone(),
1738 identity_key_pem: Some(key_pem),
1739 },
1740 [0xAA; 16],
1741 )
1742 .unwrap();
1743 let descriptor = crate::identity_token::build_identity_token_from_pem(&cert_pem, &ca_pem)
1747 .unwrap()
1748 .encode();
1749 let remote = plugin
1750 .validate_remote_identity(local, [0xBB; 16], &descriptor)
1751 .expect("descriptor must be accepted");
1752 assert_ne!(remote, local);
1753 }
1754
1755 #[test]
1756 fn validate_remote_identity_accepts_minimal_token_without_cert_properties() {
1757 let (cert_pem, ca_pem, key_pem) = make_signed_cert_ca_key();
1766 let mut plugin = PkiAuthenticationPlugin::new();
1767 let local = plugin
1768 .validate_with_config(
1769 IdentityConfig {
1770 identity_cert_pem: cert_pem,
1771 identity_ca_pem: ca_pem,
1772 identity_key_pem: Some(key_pem),
1773 },
1774 [0xAA; 16],
1775 )
1776 .unwrap();
1777 let minimal = zerodds_security::token::DataHolder::new(
1778 crate::identity_token::IDENTITY_TOKEN_CLASS_ID,
1779 )
1780 .to_cdr_le();
1781 let remote = plugin
1782 .validate_remote_identity(local, [0xBB; 16], &minimal)
1783 .expect("minimal cyclone token (only class_id) must be accepted");
1784 assert_ne!(remote, local);
1785 }
1786
1787 #[test]
1801 fn replay_cache_holds_exactly_cap_entries_before_eviction() {
1802 let (mut alice, _bob, alice_h, _, _, _) = alice_bob();
1803 for i in 0..REPLAY_CACHE_CAP {
1804 let mut c = [0u8; 32];
1805 c[0..8].copy_from_slice(&(i as u64).to_le_bytes());
1806 alice.record_challenge(alice_h, c).unwrap();
1807 }
1808 let mut c_first = [0u8; 32];
1810 c_first[0..8].copy_from_slice(&0u64.to_le_bytes());
1811 let err = alice.record_challenge(alice_h, c_first).unwrap_err();
1812 assert_eq!(err.kind, SecurityErrorKind::AuthenticationFailed);
1813 }
1814
1815 #[test]
1818 fn replay_cache_evicts_oldest_at_cap_plus_one() {
1819 let (mut alice, _bob, alice_h, _, _, _) = alice_bob();
1820 for i in 0..REPLAY_CACHE_CAP {
1821 let mut c = [0u8; 32];
1822 c[0..8].copy_from_slice(&(i as u64).to_le_bytes());
1823 alice.record_challenge(alice_h, c).unwrap();
1824 }
1825 let mut c_extra = [0u8; 32];
1827 c_extra[0..8].copy_from_slice(&(REPLAY_CACHE_CAP as u64).to_le_bytes());
1828 alice.record_challenge(alice_h, c_extra).unwrap();
1829
1830 let mut c_first = [0u8; 32];
1832 c_first[0..8].copy_from_slice(&0u64.to_le_bytes());
1833 alice
1834 .record_challenge(alice_h, c_first)
1835 .expect("after CAP+1 inserts, oldest must be evicted");
1836
1837 let mut c_recent = [0u8; 32];
1841 c_recent[0..8].copy_from_slice(&(REPLAY_CACHE_CAP as u64).to_le_bytes());
1842 let err = alice.record_challenge(alice_h, c_recent).unwrap_err();
1843 assert_eq!(err.kind, SecurityErrorKind::AuthenticationFailed);
1844 }
1845
1846 #[test]
1851 fn get_shared_secret_returns_stored_value() {
1852 let (mut alice, mut bob, alice_h, alice_remote_bob, bob_h, bob_remote_alice) = alice_bob();
1853
1854 let (alice_hs, out1) = alice
1855 .begin_handshake_request(alice_h, alice_remote_bob)
1856 .unwrap();
1857 let req = match out1 {
1858 HandshakeStepOutcome::SendMessage { token } => token,
1859 _ => panic!(),
1860 };
1861 let (bob_hs, out2) = bob
1862 .begin_handshake_reply(bob_h, bob_remote_alice, &req)
1863 .unwrap();
1864 let reply = match out2 {
1865 HandshakeStepOutcome::SendMessage { token } => token,
1866 _ => panic!(),
1867 };
1868 let out3 = alice.process_handshake(alice_hs, &reply).unwrap();
1869 let final_token = match out3 {
1870 HandshakeStepOutcome::SendMessage { token } => token,
1871 _ => panic!(),
1872 };
1873 let out4 = bob.process_handshake(bob_hs, &final_token).unwrap();
1874 let bob_handle = match out4 {
1875 HandshakeStepOutcome::Complete { secret } => secret,
1876 _ => panic!(),
1877 };
1878 let alice_handle = alice.shared_secret(alice_hs).unwrap();
1879
1880 let alice_bytes = SharedSecretProvider::get_shared_secret(&alice, alice_handle).unwrap();
1882 let bob_bytes = SharedSecretProvider::get_shared_secret(&bob, bob_handle).unwrap();
1883 assert_eq!(alice_bytes.len(), 32);
1887 assert_eq!(bob_bytes.len(), 32);
1888 assert_eq!(
1889 alice_bytes, bob_bytes,
1890 "shared secrets must be identical + non-trivial"
1891 );
1892 assert!(alice_bytes.iter().any(|&b| b != 0));
1893 assert!(alice_bytes.iter().any(|&b| b != 1));
1894
1895 let bogus_handle = zerodds_security::authentication::SharedSecretHandle(0xDEAD_BEEF);
1897 assert!(SharedSecretProvider::get_shared_secret(&alice, bogus_handle).is_none());
1898 }
1899
1900 fn run_handshake_tampered_final<M>(mutate: M)
1905 where
1906 M: FnOnce(&mut ht::DataHolder),
1907 {
1908 let (mut alice, mut bob, alice_h, alice_remote_bob, bob_h, bob_remote_alice) = alice_bob();
1909 let (alice_hs, out1) = alice
1910 .begin_handshake_request(alice_h, alice_remote_bob)
1911 .unwrap();
1912 let req = match out1 {
1913 HandshakeStepOutcome::SendMessage { token } => token,
1914 _ => panic!(),
1915 };
1916 let (bob_hs, out2) = bob
1917 .begin_handshake_reply(bob_h, bob_remote_alice, &req)
1918 .unwrap();
1919 let reply = match out2 {
1920 HandshakeStepOutcome::SendMessage { token } => token,
1921 _ => panic!(),
1922 };
1923 let out3 = alice.process_handshake(alice_hs, &reply).unwrap();
1924 let final_token = match out3 {
1925 HandshakeStepOutcome::SendMessage { token } => token,
1926 _ => panic!(),
1927 };
1928
1929 let mut h = ht::DataHolder::from_cdr_le(&final_token).unwrap();
1930 mutate(&mut h);
1931 let tampered = h.to_cdr_le();
1932 let err = bob.process_handshake(bob_hs, &tampered).unwrap_err();
1933 assert_eq!(
1934 err.kind,
1935 SecurityErrorKind::AuthenticationFailed,
1936 "tampered final token must return AuthFailed"
1937 );
1938 }
1939
1940 fn flip_first_byte(h: &mut ht::DataHolder, prop: &str) {
1941 let mut v = h.binary_property(prop).unwrap().to_vec();
1942 v[0] ^= 0x01;
1943 h.set_binary_property(prop, v);
1944 }
1945
1946 #[test]
1947 fn final_token_hash_c1_tamper_rejected() {
1948 run_handshake_tampered_final(|h| flip_first_byte(h, "hash_c1"));
1949 }
1950 #[test]
1951 fn final_token_hash_c2_tamper_rejected() {
1952 run_handshake_tampered_final(|h| flip_first_byte(h, "hash_c2"));
1953 }
1954 #[test]
1955 fn final_token_dh1_tamper_rejected() {
1956 run_handshake_tampered_final(|h| flip_first_byte(h, "dh1"));
1957 }
1958 #[test]
1959 fn final_token_dh2_tamper_rejected() {
1960 run_handshake_tampered_final(|h| flip_first_byte(h, "dh2"));
1961 }
1962 #[test]
1963 fn final_token_challenge1_tamper_rejected() {
1964 run_handshake_tampered_final(|h| flip_first_byte(h, "challenge1"));
1965 }
1966 #[test]
1967 fn final_token_challenge2_tamper_rejected() {
1968 run_handshake_tampered_final(|h| flip_first_byte(h, "challenge2"));
1969 }
1970}