1use super::error::SecurityError;
47use super::keypair::DeviceKeypair;
48use bitflags::bitflags;
49use ed25519_dalek::{Signature, Verifier, VerifyingKey};
50use std::collections::HashMap;
51
52pub const CERTIFICATE_BASE_SIZE: usize = 154;
56
57pub const MAX_CALLSIGN_LEN: usize = 16;
59
60pub const MESH_ID_LEN: usize = 8;
62
63bitflags! {
64 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
72 pub struct MemberPermissions: u8 {
73 const RELAY = 0b0000_0001;
75 const EMERGENCY = 0b0000_0010;
77 const ENROLL = 0b0000_0100;
79 const ADMIN = 0b1000_0000;
81 }
82}
83
84impl Default for MemberPermissions {
85 fn default() -> Self {
86 Self::RELAY | Self::EMERGENCY
88 }
89}
90
91impl MemberPermissions {
92 pub const STANDARD: Self = Self::RELAY.union(Self::EMERGENCY);
94
95 pub const AUTHORITY: Self = Self::all();
97}
98
99#[derive(Debug, Clone, PartialEq, Eq)]
109pub struct MembershipCertificate {
110 pub member_public_key: [u8; 32],
112
113 pub callsign: String,
116
117 pub mesh_id: String,
119
120 pub issued_at_ms: u64,
122
123 pub expires_at_ms: u64,
126
127 pub permissions: MemberPermissions,
129
130 pub issuer_public_key: [u8; 32],
133
134 pub issuer_signature: [u8; 64],
137}
138
139impl MembershipCertificate {
140 pub fn new(
144 member_public_key: [u8; 32],
145 callsign: String,
146 mesh_id: String,
147 issued_at_ms: u64,
148 expires_at_ms: u64,
149 permissions: MemberPermissions,
150 issuer_public_key: [u8; 32],
151 ) -> Self {
152 Self {
153 member_public_key,
154 callsign,
155 mesh_id,
156 issued_at_ms,
157 expires_at_ms,
158 permissions,
159 issuer_public_key,
160 issuer_signature: [0u8; 64],
161 }
162 }
163
164 pub fn new_root(
166 authority_keypair: &DeviceKeypair,
167 callsign: String,
168 mesh_id: String,
169 issued_at_ms: u64,
170 expires_at_ms: u64,
171 ) -> Self {
172 let public_key = authority_keypair.public_key_bytes();
173 let mut cert = Self::new(
174 public_key,
175 callsign,
176 mesh_id,
177 issued_at_ms,
178 expires_at_ms,
179 MemberPermissions::AUTHORITY,
180 public_key, );
182 cert.sign_with(authority_keypair);
183 cert
184 }
185
186 fn signable_bytes(&self) -> Vec<u8> {
188 let mut buf = Vec::with_capacity(CERTIFICATE_BASE_SIZE + self.callsign.len());
189
190 buf.extend_from_slice(&self.member_public_key);
192
193 buf.push(self.callsign.len() as u8);
195 buf.extend_from_slice(self.callsign.as_bytes());
196
197 buf.extend_from_slice(self.mesh_id.as_bytes());
199
200 buf.extend_from_slice(&self.issued_at_ms.to_le_bytes());
202
203 buf.extend_from_slice(&self.expires_at_ms.to_le_bytes());
205
206 buf.push(self.permissions.bits());
208
209 buf.extend_from_slice(&self.issuer_public_key);
211
212 buf
213 }
214
215 pub fn sign_with(&mut self, issuer_keypair: &DeviceKeypair) {
219 let signable = self.signable_bytes();
220 let signature = issuer_keypair.sign(&signable);
221 self.issuer_signature = signature.to_bytes();
222 }
223
224 pub fn signed(mut self, issuer_keypair: &DeviceKeypair) -> Self {
226 self.sign_with(issuer_keypair);
227 self
228 }
229
230 pub fn verify(&self) -> Result<(), SecurityError> {
232 let signable = self.signable_bytes();
233
234 let verifying_key = VerifyingKey::from_bytes(&self.issuer_public_key)
235 .map_err(|e| SecurityError::InvalidPublicKey(e.to_string()))?;
236
237 let signature = Signature::from_bytes(&self.issuer_signature);
238
239 verifying_key
240 .verify(&signable, &signature)
241 .map_err(|e| SecurityError::InvalidSignature(e.to_string()))
242 }
243
244 pub fn is_valid(&self, now_ms: u64) -> bool {
246 if self.expires_at_ms == 0 {
247 return true;
249 }
250 now_ms >= self.issued_at_ms && now_ms < self.expires_at_ms
251 }
252
253 pub fn is_in_grace_period(&self, now_ms: u64, grace_period_ms: u64) -> bool {
257 if self.expires_at_ms == 0 {
258 return false; }
260 now_ms >= self.expires_at_ms && now_ms < self.expires_at_ms + grace_period_ms
261 }
262
263 pub fn is_expired(&self, now_ms: u64, grace_period_ms: u64) -> bool {
265 if self.expires_at_ms == 0 {
266 return false; }
268 now_ms >= self.expires_at_ms + grace_period_ms
269 }
270
271 pub fn time_remaining_ms(&self, now_ms: u64) -> u64 {
273 if self.expires_at_ms == 0 || now_ms >= self.expires_at_ms {
274 0
275 } else {
276 self.expires_at_ms - now_ms
277 }
278 }
279
280 pub fn has_permission(&self, permission: MemberPermissions) -> bool {
282 self.permissions.contains(permission)
283 }
284
285 pub fn is_root(&self) -> bool {
287 self.member_public_key == self.issuer_public_key
288 }
289
290 pub fn encode(&self) -> Vec<u8> {
298 let mut buf = Vec::with_capacity(CERTIFICATE_BASE_SIZE + self.callsign.len());
299
300 buf.extend_from_slice(&self.member_public_key);
301 buf.push(self.callsign.len() as u8);
302 buf.extend_from_slice(self.callsign.as_bytes());
303 buf.extend_from_slice(self.mesh_id.as_bytes());
304 buf.extend_from_slice(&self.issued_at_ms.to_le_bytes());
305 buf.extend_from_slice(&self.expires_at_ms.to_le_bytes());
306 buf.push(self.permissions.bits());
307 buf.extend_from_slice(&self.issuer_public_key);
308 buf.extend_from_slice(&self.issuer_signature);
309
310 buf
311 }
312
313 pub fn decode(data: &[u8]) -> Result<Self, SecurityError> {
315 if data.len() < CERTIFICATE_BASE_SIZE {
317 return Err(SecurityError::SerializationError(format!(
318 "certificate too short: {} bytes, need at least {}",
319 data.len(),
320 CERTIFICATE_BASE_SIZE
321 )));
322 }
323
324 let mut offset = 0;
325
326 let mut member_public_key = [0u8; 32];
328 member_public_key.copy_from_slice(&data[offset..offset + 32]);
329 offset += 32;
330
331 let callsign_len = data[offset] as usize;
333 offset += 1;
334
335 if callsign_len > MAX_CALLSIGN_LEN {
336 return Err(SecurityError::SerializationError(format!(
337 "callsign too long: {} bytes, max {}",
338 callsign_len, MAX_CALLSIGN_LEN
339 )));
340 }
341
342 if offset + callsign_len > data.len() {
343 return Err(SecurityError::SerializationError(
344 "truncated callsign".to_string(),
345 ));
346 }
347
348 let callsign =
349 String::from_utf8(data[offset..offset + callsign_len].to_vec()).map_err(|e| {
350 SecurityError::SerializationError(format!("invalid callsign UTF-8: {}", e))
351 })?;
352 offset += callsign_len;
353
354 if offset + MESH_ID_LEN > data.len() {
356 return Err(SecurityError::SerializationError(
357 "truncated mesh_id".to_string(),
358 ));
359 }
360 let mesh_id =
361 String::from_utf8(data[offset..offset + MESH_ID_LEN].to_vec()).map_err(|e| {
362 SecurityError::SerializationError(format!("invalid mesh_id UTF-8: {}", e))
363 })?;
364 offset += MESH_ID_LEN;
365
366 if offset + 8 > data.len() {
368 return Err(SecurityError::SerializationError(
369 "truncated issued_at".to_string(),
370 ));
371 }
372 let issued_at_ms = u64::from_le_bytes(
373 data[offset..offset + 8]
374 .try_into()
375 .expect("slice length verified by preceding bounds check"),
376 );
377 offset += 8;
378
379 if offset + 8 > data.len() {
381 return Err(SecurityError::SerializationError(
382 "truncated expires_at".to_string(),
383 ));
384 }
385 let expires_at_ms = u64::from_le_bytes(
386 data[offset..offset + 8]
387 .try_into()
388 .expect("slice length verified by preceding bounds check"),
389 );
390 offset += 8;
391
392 if offset + 1 > data.len() {
394 return Err(SecurityError::SerializationError(
395 "truncated permissions".to_string(),
396 ));
397 }
398 let permissions = MemberPermissions::from_bits_truncate(data[offset]);
399 offset += 1;
400
401 if offset + 32 > data.len() {
403 return Err(SecurityError::SerializationError(
404 "truncated issuer_public_key".to_string(),
405 ));
406 }
407 let mut issuer_public_key = [0u8; 32];
408 issuer_public_key.copy_from_slice(&data[offset..offset + 32]);
409 offset += 32;
410
411 if offset + 64 > data.len() {
413 return Err(SecurityError::SerializationError(
414 "truncated signature".to_string(),
415 ));
416 }
417 let mut issuer_signature = [0u8; 64];
418 issuer_signature.copy_from_slice(&data[offset..offset + 64]);
419
420 Ok(Self {
421 member_public_key,
422 callsign,
423 mesh_id,
424 issued_at_ms,
425 expires_at_ms,
426 permissions,
427 issuer_public_key,
428 issuer_signature,
429 })
430 }
431
432 #[cfg(feature = "bluetooth")]
448 pub fn to_token(
449 &self,
450 authority_keypair: &DeviceKeypair,
451 ) -> peat_btle::security::MembershipToken {
452 use peat_btle::security::MembershipToken;
453
454 let mesh_id_bytes = Self::hex_to_bytes(&self.mesh_id);
456
457 let callsign = if self.callsign.len() > peat_btle::security::MAX_CALLSIGN_LEN {
459 &self.callsign[..peat_btle::security::MAX_CALLSIGN_LEN]
460 } else {
461 &self.callsign
462 };
463
464 let authority_identity = peat_btle::security::DeviceIdentity::from_private_key(
466 &authority_keypair.secret_key_bytes(),
467 )
468 .expect("valid keypair");
469
470 MembershipToken::issue_at(
471 &authority_identity,
472 mesh_id_bytes,
473 self.member_public_key,
474 callsign,
475 self.issued_at_ms,
476 self.expires_at_ms,
477 )
478 }
479
480 #[cfg(feature = "bluetooth")]
496 pub fn from_token(
497 token: &peat_btle::security::MembershipToken,
498 authority_keypair: &DeviceKeypair,
499 ) -> Self {
500 let mesh_id = format!(
502 "{:02X}{:02X}{:02X}{:02X}",
503 token.mesh_id[0], token.mesh_id[1], token.mesh_id[2], token.mesh_id[3]
504 );
505
506 let callsign = token.callsign_str().to_string();
507
508 let mut cert = Self::new(
509 token.public_key,
510 callsign,
511 mesh_id,
512 token.issued_at_ms,
513 token.expires_at_ms,
514 MemberPermissions::STANDARD, authority_keypair.public_key_bytes(),
516 );
517
518 cert.sign_with(authority_keypair);
519 cert
520 }
521
522 #[cfg(feature = "bluetooth")]
524 fn hex_to_bytes(hex: &str) -> [u8; 4] {
525 let mut bytes = [0u8; 4];
526 if hex.len() == 8 {
527 for (i, chunk) in hex.as_bytes().chunks(2).enumerate() {
528 if i < 4 {
529 let s = std::str::from_utf8(chunk).unwrap_or("00");
530 bytes[i] = u8::from_str_radix(s, 16).unwrap_or(0);
531 }
532 }
533 }
534 bytes
535 }
536}
537
538#[derive(Debug, Default)]
542pub struct CertificateRegistry {
543 by_public_key: HashMap<[u8; 32], MembershipCertificate>,
545
546 callsign_to_pubkey: HashMap<String, [u8; 32]>,
548}
549
550impl CertificateRegistry {
551 pub fn new() -> Self {
553 Self::default()
554 }
555
556 pub fn register(&mut self, cert: MembershipCertificate) -> Option<MembershipCertificate> {
560 let pubkey = cert.member_public_key;
561 let callsign = cert.callsign.clone();
562
563 if let Some(old_cert) = self.by_public_key.get(&pubkey) {
565 self.callsign_to_pubkey.remove(&old_cert.callsign);
566 }
567
568 self.callsign_to_pubkey.insert(callsign, pubkey);
570 self.by_public_key.insert(pubkey, cert)
571 }
572
573 pub fn get_by_pubkey(&self, pubkey: &[u8; 32]) -> Option<&MembershipCertificate> {
575 self.by_public_key.get(pubkey)
576 }
577
578 pub fn get_by_callsign(&self, callsign: &str) -> Option<&MembershipCertificate> {
580 self.callsign_to_pubkey
581 .get(callsign)
582 .and_then(|pk| self.by_public_key.get(pk))
583 }
584
585 pub fn remove(&mut self, pubkey: &[u8; 32]) -> Option<MembershipCertificate> {
587 if let Some(cert) = self.by_public_key.remove(pubkey) {
588 self.callsign_to_pubkey.remove(&cert.callsign);
589 Some(cert)
590 } else {
591 None
592 }
593 }
594
595 pub fn is_callsign_taken(&self, callsign: &str) -> bool {
597 self.callsign_to_pubkey.contains_key(callsign)
598 }
599
600 pub fn certificates(&self) -> impl Iterator<Item = &MembershipCertificate> {
602 self.by_public_key.values()
603 }
604
605 pub fn len(&self) -> usize {
607 self.by_public_key.len()
608 }
609
610 pub fn is_empty(&self) -> bool {
612 self.by_public_key.is_empty()
613 }
614
615 pub fn remove_expired(&mut self, now_ms: u64, grace_period_ms: u64) -> usize {
619 let expired: Vec<[u8; 32]> = self
620 .by_public_key
621 .iter()
622 .filter(|(_, cert)| cert.is_expired(now_ms, grace_period_ms))
623 .map(|(pk, _)| *pk)
624 .collect();
625
626 let count = expired.len();
627 for pk in expired {
628 self.remove(&pk);
629 }
630 count
631 }
632}
633
634#[cfg(test)]
635mod tests {
636 use super::*;
637
638 fn now_ms() -> u64 {
639 std::time::SystemTime::now()
640 .duration_since(std::time::UNIX_EPOCH)
641 .unwrap()
642 .as_millis() as u64
643 }
644
645 #[test]
646 fn test_create_and_sign_certificate() {
647 let authority = DeviceKeypair::generate();
648 let member = DeviceKeypair::generate();
649
650 let now = now_ms();
651 let expires = now + 24 * 60 * 60 * 1000; let cert = MembershipCertificate::new(
654 member.public_key_bytes(),
655 "ALPHA-01".to_string(),
656 "A1B2C3D4".to_string(),
657 now,
658 expires,
659 MemberPermissions::STANDARD,
660 authority.public_key_bytes(),
661 )
662 .signed(&authority);
663
664 assert!(cert.verify().is_ok());
665 assert!(cert.is_valid(now));
666 assert!(!cert.is_root());
667 }
668
669 #[test]
670 fn test_root_certificate() {
671 let authority = DeviceKeypair::generate();
672 let now = now_ms();
673 let expires = now + 24 * 60 * 60 * 1000;
674
675 let cert = MembershipCertificate::new_root(
676 &authority,
677 "COMMAND".to_string(),
678 "A1B2C3D4".to_string(),
679 now,
680 expires,
681 );
682
683 assert!(cert.verify().is_ok());
684 assert!(cert.is_root());
685 assert!(cert.has_permission(MemberPermissions::ADMIN));
686 assert!(cert.has_permission(MemberPermissions::ENROLL));
687 }
688
689 #[test]
690 fn test_certificate_encode_decode() {
691 let authority = DeviceKeypair::generate();
692 let member = DeviceKeypair::generate();
693
694 let now = now_ms();
695 let expires = now + 24 * 60 * 60 * 1000;
696
697 let cert = MembershipCertificate::new(
698 member.public_key_bytes(),
699 "BRAVO-42".to_string(),
700 "DEADBEEF".to_string(),
701 now,
702 expires,
703 MemberPermissions::RELAY | MemberPermissions::EMERGENCY,
704 authority.public_key_bytes(),
705 )
706 .signed(&authority);
707
708 let encoded = cert.encode();
709 let decoded = MembershipCertificate::decode(&encoded).unwrap();
710
711 assert_eq!(decoded.member_public_key, cert.member_public_key);
712 assert_eq!(decoded.callsign, cert.callsign);
713 assert_eq!(decoded.mesh_id, cert.mesh_id);
714 assert_eq!(decoded.issued_at_ms, cert.issued_at_ms);
715 assert_eq!(decoded.expires_at_ms, cert.expires_at_ms);
716 assert_eq!(decoded.permissions, cert.permissions);
717 assert_eq!(decoded.issuer_public_key, cert.issuer_public_key);
718 assert_eq!(decoded.issuer_signature, cert.issuer_signature);
719
720 assert!(decoded.verify().is_ok());
722 }
723
724 #[test]
725 fn test_certificate_expiration() {
726 let authority = DeviceKeypair::generate();
727 let member = DeviceKeypair::generate();
728
729 let now = 1000000u64;
730 let expires = now + 1000; let grace = 500; let cert = MembershipCertificate::new(
734 member.public_key_bytes(),
735 "TEST-01".to_string(),
736 "12345678".to_string(),
737 now,
738 expires,
739 MemberPermissions::STANDARD,
740 authority.public_key_bytes(),
741 )
742 .signed(&authority);
743
744 assert!(cert.is_valid(now + 500));
746 assert!(!cert.is_in_grace_period(now + 500, grace));
747 assert!(!cert.is_expired(now + 500, grace));
748 assert_eq!(cert.time_remaining_ms(now + 500), 500);
749
750 assert!(!cert.is_valid(expires));
752 assert!(cert.is_in_grace_period(expires, grace));
753 assert!(!cert.is_expired(expires, grace));
754
755 assert!(!cert.is_valid(expires + grace));
757 assert!(!cert.is_in_grace_period(expires + grace, grace));
758 assert!(cert.is_expired(expires + grace, grace));
759 }
760
761 #[test]
762 fn test_invalid_signature() {
763 let authority = DeviceKeypair::generate();
764 let attacker = DeviceKeypair::generate();
765 let member = DeviceKeypair::generate();
766
767 let now = now_ms();
768
769 let mut cert = MembershipCertificate::new(
771 member.public_key_bytes(),
772 "FAKE-01".to_string(),
773 "A1B2C3D4".to_string(),
774 now,
775 now + 1000,
776 MemberPermissions::ADMIN,
777 authority.public_key_bytes(), );
779 cert.sign_with(&attacker); assert!(cert.verify().is_err());
782 }
783
784 #[test]
785 fn test_tampered_certificate() {
786 let authority = DeviceKeypair::generate();
787 let member = DeviceKeypair::generate();
788
789 let now = now_ms();
790
791 let mut cert = MembershipCertificate::new(
792 member.public_key_bytes(),
793 "ALPHA-01".to_string(),
794 "A1B2C3D4".to_string(),
795 now,
796 now + 1000,
797 MemberPermissions::STANDARD,
798 authority.public_key_bytes(),
799 )
800 .signed(&authority);
801
802 cert.permissions = MemberPermissions::ADMIN;
804
805 assert!(cert.verify().is_err());
806 }
807
808 #[test]
809 fn test_certificate_registry() {
810 let authority = DeviceKeypair::generate();
811 let member1 = DeviceKeypair::generate();
812 let member2 = DeviceKeypair::generate();
813
814 let now = now_ms();
815 let expires = now + 24 * 60 * 60 * 1000;
816
817 let cert1 = MembershipCertificate::new(
818 member1.public_key_bytes(),
819 "ALPHA-01".to_string(),
820 "A1B2C3D4".to_string(),
821 now,
822 expires,
823 MemberPermissions::STANDARD,
824 authority.public_key_bytes(),
825 )
826 .signed(&authority);
827
828 let cert2 = MembershipCertificate::new(
829 member2.public_key_bytes(),
830 "BRAVO-02".to_string(),
831 "A1B2C3D4".to_string(),
832 now,
833 expires,
834 MemberPermissions::STANDARD,
835 authority.public_key_bytes(),
836 )
837 .signed(&authority);
838
839 let mut registry = CertificateRegistry::new();
840
841 assert!(registry.register(cert1.clone()).is_none());
843 assert!(registry.register(cert2.clone()).is_none());
844 assert_eq!(registry.len(), 2);
845
846 let found = registry.get_by_pubkey(&member1.public_key_bytes()).unwrap();
848 assert_eq!(found.callsign, "ALPHA-01");
849
850 let found = registry.get_by_callsign("BRAVO-02").unwrap();
852 assert_eq!(found.member_public_key, member2.public_key_bytes());
853
854 assert!(registry.is_callsign_taken("ALPHA-01"));
856 assert!(!registry.is_callsign_taken("CHARLIE-03"));
857
858 let removed = registry.remove(&member1.public_key_bytes());
860 assert!(removed.is_some());
861 assert_eq!(registry.len(), 1);
862 assert!(!registry.is_callsign_taken("ALPHA-01"));
863 }
864
865 #[test]
866 fn test_registry_remove_expired() {
867 let authority = DeviceKeypair::generate();
868 let member1 = DeviceKeypair::generate();
869 let member2 = DeviceKeypair::generate();
870
871 let now = 1000000u64;
872 let grace = 1000u64;
873
874 let cert1 = MembershipCertificate::new(
876 member1.public_key_bytes(),
877 "EXPIRED-01".to_string(),
878 "A1B2C3D4".to_string(),
879 now - 10000,
880 now - 5000, MemberPermissions::STANDARD,
882 authority.public_key_bytes(),
883 )
884 .signed(&authority);
885
886 let cert2 = MembershipCertificate::new(
888 member2.public_key_bytes(),
889 "VALID-02".to_string(),
890 "A1B2C3D4".to_string(),
891 now,
892 now + 10000, MemberPermissions::STANDARD,
894 authority.public_key_bytes(),
895 )
896 .signed(&authority);
897
898 let mut registry = CertificateRegistry::new();
899 registry.register(cert1);
900 registry.register(cert2);
901 assert_eq!(registry.len(), 2);
902
903 let removed = registry.remove_expired(now, grace);
905 assert_eq!(removed, 1);
906 assert_eq!(registry.len(), 1);
907 assert!(registry.get_by_callsign("VALID-02").is_some());
908 assert!(registry.get_by_callsign("EXPIRED-01").is_none());
909 }
910
911 #[test]
912 fn test_permissions() {
913 assert!(MemberPermissions::STANDARD.contains(MemberPermissions::RELAY));
914 assert!(MemberPermissions::STANDARD.contains(MemberPermissions::EMERGENCY));
915 assert!(!MemberPermissions::STANDARD.contains(MemberPermissions::ENROLL));
916 assert!(!MemberPermissions::STANDARD.contains(MemberPermissions::ADMIN));
917
918 assert!(MemberPermissions::AUTHORITY.contains(MemberPermissions::RELAY));
919 assert!(MemberPermissions::AUTHORITY.contains(MemberPermissions::EMERGENCY));
920 assert!(MemberPermissions::AUTHORITY.contains(MemberPermissions::ENROLL));
921 assert!(MemberPermissions::AUTHORITY.contains(MemberPermissions::ADMIN));
922 }
923
924 #[cfg(feature = "bluetooth")]
925 mod token_conversion_tests {
926 use super::*;
927
928 #[test]
929 fn test_certificate_to_token() {
930 let authority = DeviceKeypair::generate();
931 let member = DeviceKeypair::generate();
932
933 let now = 1000000u64;
934 let expires = now + 86_400_000; let cert = MembershipCertificate::new(
937 member.public_key_bytes(),
938 "ALPHA-07".to_string(),
939 "A1B2C3D4".to_string(),
940 now,
941 expires,
942 MemberPermissions::STANDARD,
943 authority.public_key_bytes(),
944 )
945 .signed(&authority);
946
947 let token = cert.to_token(&authority);
949
950 assert_eq!(token.public_key, member.public_key_bytes());
952 assert_eq!(token.callsign_str(), "ALPHA-07");
953 assert_eq!(token.mesh_id_hex(), "A1B2C3D4");
954 assert_eq!(token.issued_at_ms, now);
955 assert_eq!(token.expires_at_ms, expires);
956
957 let authority_identity = peat_btle::security::DeviceIdentity::from_private_key(
959 &authority.secret_key_bytes(),
960 )
961 .unwrap();
962 assert!(token.verify(&authority_identity.public_key()));
963 }
964
965 #[test]
966 fn test_token_to_certificate() {
967 let authority = DeviceKeypair::generate();
968 let member_pubkey = DeviceKeypair::generate().public_key_bytes();
969
970 let authority_identity = peat_btle::security::DeviceIdentity::from_private_key(
972 &authority.secret_key_bytes(),
973 )
974 .unwrap();
975
976 let mesh_id = [0xA1, 0xB2, 0xC3, 0xD4];
977 let now = 1000000u64;
978 let expires = now + 86_400_000;
979
980 let token = peat_btle::security::MembershipToken::issue_at(
981 &authority_identity,
982 mesh_id,
983 member_pubkey,
984 "BRAVO-03",
985 now,
986 expires,
987 );
988
989 let cert = MembershipCertificate::from_token(&token, &authority);
991
992 assert_eq!(cert.member_public_key, member_pubkey);
994 assert_eq!(cert.callsign, "BRAVO-03");
995 assert_eq!(cert.mesh_id, "A1B2C3D4");
996 assert_eq!(cert.issued_at_ms, now);
997 assert_eq!(cert.expires_at_ms, expires);
998 assert_eq!(cert.permissions, MemberPermissions::STANDARD);
999 assert_eq!(cert.issuer_public_key, authority.public_key_bytes());
1000
1001 assert!(cert.verify().is_ok());
1003 }
1004
1005 #[test]
1006 fn test_roundtrip_conversion() {
1007 let authority = DeviceKeypair::generate();
1008 let member = DeviceKeypair::generate();
1009
1010 let now = 1000000u64;
1011 let expires = now + 86_400_000;
1012
1013 let original_cert = MembershipCertificate::new(
1015 member.public_key_bytes(),
1016 "CHARLIE-99".to_string(),
1017 "DEADBEEF".to_string(),
1018 now,
1019 expires,
1020 MemberPermissions::RELAY | MemberPermissions::EMERGENCY | MemberPermissions::ENROLL,
1021 authority.public_key_bytes(),
1022 )
1023 .signed(&authority);
1024
1025 let token = original_cert.to_token(&authority);
1027
1028 let recovered_cert = MembershipCertificate::from_token(&token, &authority);
1030
1031 assert_eq!(
1033 recovered_cert.member_public_key,
1034 original_cert.member_public_key
1035 );
1036 assert_eq!(recovered_cert.callsign, original_cert.callsign);
1037 assert_eq!(recovered_cert.mesh_id, original_cert.mesh_id);
1038 assert_eq!(recovered_cert.issued_at_ms, original_cert.issued_at_ms);
1039 assert_eq!(recovered_cert.expires_at_ms, original_cert.expires_at_ms);
1040
1041 assert_eq!(recovered_cert.permissions, MemberPermissions::STANDARD);
1043
1044 assert!(original_cert.verify().is_ok());
1046 assert!(recovered_cert.verify().is_ok());
1047 }
1048
1049 #[test]
1050 fn test_long_callsign_truncation() {
1051 let authority = DeviceKeypair::generate();
1052 let member = DeviceKeypair::generate();
1053
1054 let cert = MembershipCertificate::new(
1056 member.public_key_bytes(),
1057 "ALPHA-BRAVO-1234".to_string(), "A1B2C3D4".to_string(),
1059 1000,
1060 2000,
1061 MemberPermissions::STANDARD,
1062 authority.public_key_bytes(),
1063 )
1064 .signed(&authority);
1065
1066 let token = cert.to_token(&authority);
1068
1069 assert_eq!(token.callsign_str(), "ALPHA-BRAVO-");
1071 assert_eq!(token.callsign_str().len(), 12);
1072 }
1073 }
1074}