1extern crate alloc;
43
44use alloc::string::{String, ToString};
45use alloc::vec::Vec;
46
47use crate::error::{SecurityError, SecurityErrorKind, SecurityResult};
48
49#[derive(Debug, Clone, PartialEq, Eq, Default)]
53pub struct WireProperty {
54 pub name: String,
56 pub value: String,
58}
59
60impl WireProperty {
61 #[must_use]
63 pub fn new(name: impl Into<String>, value: impl Into<String>) -> Self {
64 Self {
65 name: name.into(),
66 value: value.into(),
67 }
68 }
69}
70
71#[derive(Debug, Clone, PartialEq, Eq, Default)]
74pub struct BinaryProperty {
75 pub name: String,
77 pub value: Vec<u8>,
79}
80
81impl BinaryProperty {
82 #[must_use]
84 pub fn new(name: impl Into<String>, value: impl Into<Vec<u8>>) -> Self {
85 Self {
86 name: name.into(),
87 value: value.into(),
88 }
89 }
90}
91
92#[derive(Debug, Clone, PartialEq, Eq, Default)]
96pub struct DataHolder {
97 pub class_id: String,
99 pub properties: Vec<WireProperty>,
101 pub binary_properties: Vec<BinaryProperty>,
103}
104
105impl DataHolder {
106 #[must_use]
108 pub fn new(class_id: impl Into<String>) -> Self {
109 Self {
110 class_id: class_id.into(),
111 properties: Vec::new(),
112 binary_properties: Vec::new(),
113 }
114 }
115
116 #[must_use]
118 pub fn with_property(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
119 self.properties.push(WireProperty::new(name, value));
120 self
121 }
122
123 #[must_use]
125 pub fn with_binary_property(
126 mut self,
127 name: impl Into<String>,
128 value: impl Into<Vec<u8>>,
129 ) -> Self {
130 self.binary_properties
131 .push(BinaryProperty::new(name, value));
132 self
133 }
134
135 pub fn set_property(&mut self, name: impl Into<String>, value: impl Into<String>) {
140 let n = name.into();
141 if let Some(existing) = self.properties.iter_mut().find(|p| p.name == n) {
142 existing.value = value.into();
143 } else {
144 self.properties.push(WireProperty {
145 name: n,
146 value: value.into(),
147 });
148 }
149 }
150
151 pub fn set_binary_property(&mut self, name: impl Into<String>, value: impl Into<Vec<u8>>) {
153 let n = name.into();
154 if let Some(existing) = self.binary_properties.iter_mut().find(|p| p.name == n) {
155 existing.value = value.into();
156 } else {
157 self.binary_properties.push(BinaryProperty {
158 name: n,
159 value: value.into(),
160 });
161 }
162 }
163
164 #[must_use]
166 pub fn property(&self, name: &str) -> Option<&str> {
167 self.properties
168 .iter()
169 .find(|p| p.name == name)
170 .map(|p| p.value.as_str())
171 }
172
173 #[must_use]
175 pub fn binary_property(&self, name: &str) -> Option<&[u8]> {
176 self.binary_properties
177 .iter()
178 .find(|p| p.name == name)
179 .map(|p| p.value.as_slice())
180 }
181
182 #[must_use]
186 pub fn to_cdr_le(&self) -> Vec<u8> {
187 encode(self, true)
188 }
189
190 #[must_use]
192 pub fn to_cdr_be(&self) -> Vec<u8> {
193 encode(self, false)
194 }
195
196 pub fn from_cdr_le(bytes: &[u8]) -> SecurityResult<Self> {
202 decode(bytes, true)
203 }
204
205 pub fn from_cdr_le_consumed(bytes: &[u8]) -> SecurityResult<(Self, usize)> {
213 decode_consumed(bytes, true)
214 }
215
216 pub fn from_cdr_be(bytes: &[u8]) -> SecurityResult<Self> {
221 decode(bytes, false)
222 }
223}
224
225pub type IdentityToken = DataHolder;
232
233pub type PermissionsToken = DataHolder;
236
237pub type IdentityStatusToken = DataHolder;
240
241pub type PermissionsCredentialToken = DataHolder;
244
245pub type AuthRequestMessageToken = DataHolder;
247
248pub type HandshakeMessageToken = DataHolder;
250
251pub type CryptoToken = DataHolder;
253
254pub mod class_id {
260 pub const AUTH_PKI_DH_V12: &str = "DDS:Auth:PKI-DH:1.2";
262 pub const ACCESS_PERMISSIONS_V12: &str = "DDS:Access:Permissions:1.2";
264 pub const CRYPTO_AES_GCM_GMAC_V12: &str = "DDS:Crypto:AES-GCM-GMAC:1.2";
266 pub const ACCESS_PERMISSIONS_CREDENTIAL: &str = "DDS:Access:PermissionsCredential";
268}
269
270pub mod prop {
272 pub const CERT_SN: &str = "dds.cert.sn";
274 pub const CERT_ALGO: &str = "dds.cert.algo";
276 pub const CA_SN: &str = "dds.ca.sn";
278 pub const CA_ALGO: &str = "dds.ca.algo";
280 pub const PERM_CA_SN: &str = "dds.perm_ca.sn";
283 pub const PERM_CA_ALGO: &str = "dds.perm_ca.algo";
285}
286
287impl IdentityToken {
288 #[must_use]
292 pub fn pki_dh_v12(
293 cert_sn: impl Into<String>,
294 cert_algo: impl Into<String>,
295 ca_sn: impl Into<String>,
296 ca_algo: impl Into<String>,
297 ) -> Self {
298 DataHolder::new(class_id::AUTH_PKI_DH_V12)
299 .with_property(prop::CERT_SN, cert_sn)
300 .with_property(prop::CERT_ALGO, cert_algo)
301 .with_property(prop::CA_SN, ca_sn)
302 .with_property(prop::CA_ALGO, ca_algo)
303 }
304
305 #[must_use]
309 pub fn permissions_v12(perm_ca_sn: impl Into<String>, perm_ca_algo: impl Into<String>) -> Self {
310 DataHolder::new(class_id::ACCESS_PERMISSIONS_V12)
311 .with_property(prop::PERM_CA_SN, perm_ca_sn)
312 .with_property(prop::PERM_CA_ALGO, perm_ca_algo)
313 }
314}
315
316const MAX_TOKEN_BYTES: usize = 64 * 1024;
336const MAX_PROPS: u32 = 256;
337const MAX_BIN_PROPS: u32 = 256;
338const MAX_STRING_LEN: u32 = 8 * 1024;
339const MAX_BINARY_LEN: u32 = 8 * 1024;
340
341fn encode(d: &DataHolder, le: bool) -> Vec<u8> {
342 let mut out = Vec::with_capacity(64);
343 encode_string(&mut out, &d.class_id, le);
344 encode_u32(&mut out, d.properties.len() as u32, le);
345 for p in &d.properties {
346 encode_string(&mut out, &p.name, le);
347 encode_string(&mut out, &p.value, le);
348 }
349 encode_u32(&mut out, d.binary_properties.len() as u32, le);
350 for p in &d.binary_properties {
351 encode_string(&mut out, &p.name, le);
352 encode_octet_seq(&mut out, &p.value, le);
353 }
354 out
355}
356
357fn decode(bytes: &[u8], le: bool) -> SecurityResult<DataHolder> {
358 decode_consumed(bytes, le).map(|(dh, _)| dh)
359}
360
361fn decode_consumed(bytes: &[u8], le: bool) -> SecurityResult<(DataHolder, usize)> {
366 if bytes.len() > MAX_TOKEN_BYTES {
367 return Err(SecurityError::new(
368 SecurityErrorKind::BadArgument,
369 "token: payload exceeds DoS cap",
370 ));
371 }
372 let mut cur = Cursor::new(bytes);
373 let class_id = cur.read_string(le)?;
374 let prop_count = cur.read_u32(le)?;
375 if prop_count > MAX_PROPS {
376 return Err(SecurityError::new(
377 SecurityErrorKind::BadArgument,
378 "token: property count exceeds cap",
379 ));
380 }
381 let mut properties = Vec::with_capacity(prop_count as usize);
382 for _ in 0..prop_count {
383 let name = cur.read_string(le)?;
384 let value = cur.read_string(le)?;
385 properties.push(WireProperty { name, value });
386 }
387 let bin_count = cur.read_u32(le)?;
388 if bin_count > MAX_BIN_PROPS {
389 return Err(SecurityError::new(
390 SecurityErrorKind::BadArgument,
391 "token: binary_property count exceeds cap",
392 ));
393 }
394 let mut binary_properties = Vec::with_capacity(bin_count as usize);
395 for _ in 0..bin_count {
396 let name = cur.read_string(le)?;
397 let value = cur.read_octet_seq(le)?;
398 binary_properties.push(BinaryProperty { name, value });
399 }
400 Ok((
401 DataHolder {
402 class_id,
403 properties,
404 binary_properties,
405 },
406 cur.pos,
407 ))
408}
409
410fn align_to(out: &mut Vec<u8>, n: usize) {
411 let pad = (n - out.len() % n) % n;
412 for _ in 0..pad {
413 out.push(0);
414 }
415}
416
417fn encode_u32(out: &mut Vec<u8>, v: u32, le: bool) {
418 align_to(out, 4);
419 if le {
420 out.extend_from_slice(&v.to_le_bytes());
421 } else {
422 out.extend_from_slice(&v.to_be_bytes());
423 }
424}
425
426fn encode_string(out: &mut Vec<u8>, s: &str, le: bool) {
427 let bytes = s.as_bytes();
428 let len = (bytes.len() + 1) as u32;
429 encode_u32(out, len, le);
430 out.extend_from_slice(bytes);
431 out.push(0);
432}
433
434fn encode_octet_seq(out: &mut Vec<u8>, v: &[u8], le: bool) {
435 encode_u32(out, v.len() as u32, le);
436 out.extend_from_slice(v);
437}
438
439struct Cursor<'a> {
440 buf: &'a [u8],
441 pos: usize,
442}
443
444impl<'a> Cursor<'a> {
445 fn new(buf: &'a [u8]) -> Self {
446 Self { buf, pos: 0 }
447 }
448
449 fn align_to(&mut self, n: usize) {
450 let pad = (n - self.pos % n) % n;
451 self.pos = self.pos.saturating_add(pad);
452 }
453
454 fn read_u32(&mut self, le: bool) -> SecurityResult<u32> {
455 self.align_to(4);
456 if self.pos + 4 > self.buf.len() {
457 return Err(SecurityError::new(
458 SecurityErrorKind::BadArgument,
459 "token: truncated u32",
460 ));
461 }
462 let raw = [
463 self.buf[self.pos],
464 self.buf[self.pos + 1],
465 self.buf[self.pos + 2],
466 self.buf[self.pos + 3],
467 ];
468 self.pos += 4;
469 Ok(if le {
470 u32::from_le_bytes(raw)
471 } else {
472 u32::from_be_bytes(raw)
473 })
474 }
475
476 fn read_string(&mut self, le: bool) -> SecurityResult<String> {
477 let len = self.read_u32(le)?;
478 if len > MAX_STRING_LEN {
479 return Err(SecurityError::new(
480 SecurityErrorKind::BadArgument,
481 "token: string exceeds cap",
482 ));
483 }
484 if len == 0 {
485 return Ok(String::new());
486 }
487 if self.pos + len as usize > self.buf.len() {
488 return Err(SecurityError::new(
489 SecurityErrorKind::BadArgument,
490 "token: truncated string",
491 ));
492 }
493 let body = &self.buf[self.pos..self.pos + len as usize];
494 self.pos += len as usize;
495 if let Some((_, rest)) = body.split_last() {
497 if body.last() != Some(&0) {
498 return Err(SecurityError::new(
499 SecurityErrorKind::BadArgument,
500 "token: string missing trailing NUL",
501 ));
502 }
503 let s = core::str::from_utf8(rest).map_err(|_| {
504 SecurityError::new(SecurityErrorKind::BadArgument, "token: string not UTF-8")
505 })?;
506 Ok(s.to_string())
507 } else {
508 Err(SecurityError::new(
509 SecurityErrorKind::BadArgument,
510 "token: zero-length string body",
511 ))
512 }
513 }
514
515 fn read_octet_seq(&mut self, le: bool) -> SecurityResult<Vec<u8>> {
516 let len = self.read_u32(le)?;
517 if len > MAX_BINARY_LEN {
518 return Err(SecurityError::new(
519 SecurityErrorKind::BadArgument,
520 "token: binary value exceeds cap",
521 ));
522 }
523 if self.pos + len as usize > self.buf.len() {
524 return Err(SecurityError::new(
525 SecurityErrorKind::BadArgument,
526 "token: truncated binary",
527 ));
528 }
529 let v = self.buf[self.pos..self.pos + len as usize].to_vec();
530 self.pos += len as usize;
531 Ok(v)
532 }
533}
534
535#[cfg(test)]
536#[allow(clippy::expect_used, clippy::unwrap_used)]
537mod tests {
538 use super::*;
539
540 #[test]
541 fn empty_data_holder_roundtrip_le() {
542 let dh = DataHolder::new("DDS:Auth:PKI-DH:1.2");
543 let bytes = dh.to_cdr_le();
544 let back = DataHolder::from_cdr_le(&bytes).unwrap();
545 assert_eq!(dh, back);
546 }
547
548 #[test]
549 fn empty_data_holder_roundtrip_be() {
550 let dh = DataHolder::new("DDS:Auth:PKI-DH:1.2");
551 let bytes = dh.to_cdr_be();
552 let back = DataHolder::from_cdr_be(&bytes).unwrap();
553 assert_eq!(dh, back);
554 }
555
556 #[test]
557 fn pki_dh_identity_token_roundtrip() {
558 let tok =
559 IdentityToken::pki_dh_v12("01:23:45:67", "ECDSA-SHA256", "FA:CE:0B:01", "RSA-SHA256");
560 let bytes = tok.to_cdr_le();
561 let back = DataHolder::from_cdr_le(&bytes).unwrap();
562 assert_eq!(tok, back);
563 assert_eq!(back.class_id, "DDS:Auth:PKI-DH:1.2");
564 assert_eq!(back.property("dds.cert.sn"), Some("01:23:45:67"));
565 assert_eq!(back.property("dds.cert.algo"), Some("ECDSA-SHA256"));
566 assert_eq!(back.property("dds.ca.sn"), Some("FA:CE:0B:01"));
567 assert_eq!(back.property("dds.ca.algo"), Some("RSA-SHA256"));
568 assert!(back.binary_properties.is_empty());
569 }
570
571 #[test]
572 fn permissions_token_roundtrip() {
573 let tok = IdentityToken::permissions_v12("DE:AD:BE:EF", "ECDSA-SHA256");
574 let le = tok.to_cdr_le();
575 let be = tok.to_cdr_be();
576 assert_eq!(tok, DataHolder::from_cdr_le(&le).unwrap());
577 assert_eq!(tok, DataHolder::from_cdr_be(&be).unwrap());
578 assert_ne!(le, be, "BE/LE streams differ");
579 }
580
581 #[test]
582 fn token_with_binary_property_roundtrip() {
583 let tok = DataHolder::new("DDS:Auth:PKI-DH:1.2")
584 .with_property("dds.cert.sn", "01:23")
585 .with_binary_property("dds.cert.bytes", vec![0xCA, 0xFE, 0xBA, 0xBE, 0xDE]);
586 let bytes = tok.to_cdr_le();
587 let back = DataHolder::from_cdr_le(&bytes).unwrap();
588 assert_eq!(tok, back);
589 assert_eq!(
590 back.binary_property("dds.cert.bytes"),
591 Some(&[0xCA, 0xFE, 0xBA, 0xBE, 0xDE][..])
592 );
593 }
594
595 #[test]
596 fn cdr_le_layout_class_id_only() {
597 let dh = DataHolder::new("A");
604 let bytes = dh.to_cdr_le();
605 assert_eq!(
606 bytes,
607 vec![
608 0x02, 0x00, 0x00, 0x00, b'A', 0x00, 0x00, 0x00, 0, 0, 0, 0, 0, 0, 0, 0
609 ]
610 );
611 }
612
613 #[test]
614 fn truncated_buffer_is_error() {
615 let err = DataHolder::from_cdr_le(&[0x10, 0x00, 0x00]).unwrap_err();
616 assert_eq!(err.kind, SecurityErrorKind::BadArgument);
617 }
618
619 #[test]
620 fn property_count_cap_rejects_huge() {
621 let mut bytes = Vec::new();
623 encode_string(&mut bytes, "X", true);
624 encode_u32(&mut bytes, 1_000_000, true); let err = DataHolder::from_cdr_le(&bytes).unwrap_err();
626 assert_eq!(err.kind, SecurityErrorKind::BadArgument);
627 }
628
629 #[test]
630 fn missing_trailing_nul_rejected() {
631 let bytes = vec![0x01, 0x00, 0x00, 0x00, b'A'];
633 let err = DataHolder::from_cdr_le(&bytes).unwrap_err();
634 assert_eq!(err.kind, SecurityErrorKind::BadArgument);
635 }
636
637 #[test]
638 fn dos_cap_overall_payload() {
639 let big = vec![0u8; MAX_TOKEN_BYTES + 1];
640 let err = DataHolder::from_cdr_le(&big).unwrap_err();
641 assert_eq!(err.kind, SecurityErrorKind::BadArgument);
642 }
643}