Skip to main content

peat_protocol/security/
membership.rs

1//! Membership certificates for tactical mesh networks (ADR-048).
2//!
3//! Provides authority-issued certificates binding device identity to callsign,
4//! with time-limited validity and permission-based access control.
5//!
6//! # Trust Model
7//!
8//! ```text
9//! MeshGenesis (root of trust)
10//!     │
11//!     └── creator_public_key (authority)
12//!             │
13//!             └── signs ──► MembershipCertificate
14//!                               ├── member_public_key ──► derives node_id
15//!                               ├── callsign (authority-assigned)
16//!                               ├── expires_at_ms
17//!                               └── permissions
18//! ```
19//!
20//! # Example
21//!
22//! ```ignore
23//! use peat_protocol::security::{DeviceKeypair, MembershipCertificate, MemberPermissions};
24//!
25//! // Authority creates certificate for new member
26//! let authority = DeviceKeypair::generate();
27//! let member = DeviceKeypair::generate();
28//!
29//! let cert = MembershipCertificate::new(
30//!     member.public_key_bytes(),
31//!     "ALPHA-01".to_string(),
32//!     "A1B2C3D4".to_string(),
33//!     now_ms,
34//!     now_ms + 24 * 60 * 60 * 1000,  // 24 hours
35//!     MemberPermissions::RELAY | MemberPermissions::EMERGENCY,
36//!     authority.public_key_bytes(),
37//! );
38//!
39//! // Sign with authority's key
40//! let signed_cert = cert.sign(&authority);
41//!
42//! // Verify certificate
43//! assert!(signed_cert.verify().is_ok());
44//! ```
45
46use super::error::SecurityError;
47use super::keypair::DeviceKeypair;
48use bitflags::bitflags;
49use ed25519_dalek::{Signature, Verifier, VerifyingKey};
50use std::collections::HashMap;
51
52/// Size of membership certificate wire format (without variable callsign)
53/// Base: 32 (pubkey) + 1 (callsign_len) + 8 (mesh_id) + 8 (issued) + 8 (expires) + 1 (perms) + 32 (issuer) + 64 (sig) = 154
54/// Plus callsign bytes (max 16)
55pub const CERTIFICATE_BASE_SIZE: usize = 154;
56
57/// Maximum callsign length in bytes
58pub const MAX_CALLSIGN_LEN: usize = 16;
59
60/// Mesh ID length (8 hex characters = 4 bytes, but stored as 8-char string)
61pub const MESH_ID_LEN: usize = 8;
62
63bitflags! {
64    /// Permission flags for mesh members.
65    ///
66    /// These flags control what operations a member can perform:
67    /// - `RELAY`: Can relay messages for other nodes
68    /// - `EMERGENCY`: Can trigger emergency alerts
69    /// - `ENROLL`: Can enroll new members (delegation)
70    /// - `ADMIN`: Full administrative privileges
71    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
72    pub struct MemberPermissions: u8 {
73        /// Can relay messages for other nodes
74        const RELAY      = 0b0000_0001;
75        /// Can trigger emergency alerts
76        const EMERGENCY  = 0b0000_0010;
77        /// Can enroll new members (delegation of authority)
78        const ENROLL     = 0b0000_0100;
79        /// Full administrative privileges
80        const ADMIN      = 0b1000_0000;
81    }
82}
83
84impl Default for MemberPermissions {
85    fn default() -> Self {
86        // Default: can relay and send emergencies, but not enroll or admin
87        Self::RELAY | Self::EMERGENCY
88    }
89}
90
91impl MemberPermissions {
92    /// Standard member permissions (relay + emergency)
93    pub const STANDARD: Self = Self::RELAY.union(Self::EMERGENCY);
94
95    /// Authority permissions (all flags)
96    pub const AUTHORITY: Self = Self::all();
97}
98
99/// A membership certificate binding device identity to callsign.
100///
101/// Issued by mesh authority, contains:
102/// - Member's Ed25519 public key
103/// - Authority-assigned callsign
104/// - Mesh identifier
105/// - Validity period (issued_at to expires_at)
106/// - Permission flags
107/// - Issuer's public key and signature
108#[derive(Debug, Clone, PartialEq, Eq)]
109pub struct MembershipCertificate {
110    /// Member's Ed25519 public key (32 bytes)
111    pub member_public_key: [u8; 32],
112
113    /// Authority-assigned callsign (max 16 UTF-8 bytes)
114    /// Examples: "ALPHA-01", "BRAVO-42", "ZULU-99"
115    pub callsign: String,
116
117    /// Mesh identifier (8 hex characters)
118    pub mesh_id: String,
119
120    /// Timestamp when certificate was issued (ms since Unix epoch)
121    pub issued_at_ms: u64,
122
123    /// Timestamp when certificate expires (ms since Unix epoch)
124    /// 0 = no expiration (not recommended for production)
125    pub expires_at_ms: u64,
126
127    /// Permission flags
128    pub permissions: MemberPermissions,
129
130    /// Issuer's Ed25519 public key (32 bytes)
131    /// For root certificates, this equals member_public_key (self-signed)
132    pub issuer_public_key: [u8; 32],
133
134    /// Ed25519 signature over all above fields (64 bytes)
135    /// Empty until signed
136    pub issuer_signature: [u8; 64],
137}
138
139impl MembershipCertificate {
140    /// Create a new unsigned certificate.
141    ///
142    /// Call `sign()` with the issuer's keypair to complete the certificate.
143    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    /// Create a self-signed root certificate (for mesh authority).
165    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, // Self-signed
181        );
182        cert.sign_with(authority_keypair);
183        cert
184    }
185
186    /// Get the bytes that are signed (everything except the signature).
187    fn signable_bytes(&self) -> Vec<u8> {
188        let mut buf = Vec::with_capacity(CERTIFICATE_BASE_SIZE + self.callsign.len());
189
190        // member_public_key (32)
191        buf.extend_from_slice(&self.member_public_key);
192
193        // callsign_len (1) + callsign (variable)
194        buf.push(self.callsign.len() as u8);
195        buf.extend_from_slice(self.callsign.as_bytes());
196
197        // mesh_id (8 bytes as UTF-8)
198        buf.extend_from_slice(self.mesh_id.as_bytes());
199
200        // issued_at_ms (8)
201        buf.extend_from_slice(&self.issued_at_ms.to_le_bytes());
202
203        // expires_at_ms (8)
204        buf.extend_from_slice(&self.expires_at_ms.to_le_bytes());
205
206        // permissions (1)
207        buf.push(self.permissions.bits());
208
209        // issuer_public_key (32)
210        buf.extend_from_slice(&self.issuer_public_key);
211
212        buf
213    }
214
215    /// Sign this certificate with the issuer's keypair.
216    ///
217    /// Modifies the certificate in place.
218    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    /// Create a signed copy of this certificate.
225    pub fn signed(mut self, issuer_keypair: &DeviceKeypair) -> Self {
226        self.sign_with(issuer_keypair);
227        self
228    }
229
230    /// Verify the certificate signature against the issuer's public key.
231    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    /// Check if the certificate is currently valid (not expired).
245    pub fn is_valid(&self, now_ms: u64) -> bool {
246        if self.expires_at_ms == 0 {
247            // No expiration set
248            return true;
249        }
250        now_ms >= self.issued_at_ms && now_ms < self.expires_at_ms
251    }
252
253    /// Check if the certificate is within the grace period.
254    ///
255    /// Grace period allows continued operation for a short time after expiration.
256    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; // No expiration = no grace period
259        }
260        now_ms >= self.expires_at_ms && now_ms < self.expires_at_ms + grace_period_ms
261    }
262
263    /// Check if the certificate has expired beyond the grace period.
264    pub fn is_expired(&self, now_ms: u64, grace_period_ms: u64) -> bool {
265        if self.expires_at_ms == 0 {
266            return false; // No expiration
267        }
268        now_ms >= self.expires_at_ms + grace_period_ms
269    }
270
271    /// Get time remaining until expiration (0 if expired).
272    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    /// Check if the member has a specific permission.
281    pub fn has_permission(&self, permission: MemberPermissions) -> bool {
282        self.permissions.contains(permission)
283    }
284
285    /// Check if this is a self-signed root certificate.
286    pub fn is_root(&self) -> bool {
287        self.member_public_key == self.issuer_public_key
288    }
289
290    /// Encode certificate to wire format.
291    ///
292    /// Format:
293    /// ```text
294    /// [member_pubkey:32][callsign_len:1][callsign:N][mesh_id:8]
295    /// [issued_at:8][expires_at:8][permissions:1][issuer_pubkey:32][signature:64]
296    /// ```
297    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    /// Decode certificate from wire format.
314    pub fn decode(data: &[u8]) -> Result<Self, SecurityError> {
315        // Minimum size: base size with empty callsign
316        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        // member_public_key (32)
327        let mut member_public_key = [0u8; 32];
328        member_public_key.copy_from_slice(&data[offset..offset + 32]);
329        offset += 32;
330
331        // callsign_len (1) + callsign (variable)
332        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        // mesh_id (8)
355        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        // issued_at_ms (8)
367        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        // expires_at_ms (8)
380        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        // permissions (1)
393        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        // issuer_public_key (32)
402        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        // issuer_signature (64)
412        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    /// Convert to a lightweight MembershipToken for constrained devices.
433    ///
434    /// This creates a new token with the authority's signature. The token has:
435    /// - Callsign truncated to 12 characters (if longer)
436    /// - mesh_id converted from 8-char hex to 4-byte binary
437    /// - No permission field (tokens don't carry permissions)
438    ///
439    /// # Arguments
440    /// * `authority_keypair` - The authority's keypair to sign the token
441    ///
442    /// # Example
443    /// ```ignore
444    /// let token = certificate.to_token(&authority_keypair);
445    /// // Send token over BLE to WearTAK
446    /// ```
447    #[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        // Convert 8-char hex mesh_id to 4 bytes
455        let mesh_id_bytes = Self::hex_to_bytes(&self.mesh_id);
456
457        // Truncate callsign to 12 chars if needed
458        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        // Create a DeviceIdentity from the authority keypair for signing
465        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    /// Create a MembershipCertificate from a MembershipToken.
481    ///
482    /// This upgrades a lightweight token to a full certificate with:
483    /// - mesh_id expanded from 4-byte binary to 8-char hex
484    /// - Default permissions (STANDARD: RELAY | EMERGENCY)
485    /// - The authority's signature (re-signed for certificate format)
486    ///
487    /// # Arguments
488    /// * `token` - The token to convert
489    /// * `authority_keypair` - The authority's keypair to sign the certificate
490    ///
491    /// # Example
492    /// ```ignore
493    /// let cert = MembershipCertificate::from_token(&token, &authority_keypair);
494    /// ```
495    #[cfg(feature = "bluetooth")]
496    pub fn from_token(
497        token: &peat_btle::security::MembershipToken,
498        authority_keypair: &DeviceKeypair,
499    ) -> Self {
500        // Convert 4-byte mesh_id to 8-char hex
501        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, // Default permissions
515            authority_keypair.public_key_bytes(),
516        );
517
518        cert.sign_with(authority_keypair);
519        cert
520    }
521
522    /// Helper to convert 8-char hex string to 4 bytes.
523    #[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/// Registry for storing and looking up membership certificates.
539///
540/// Provides O(1) lookup by member public key or callsign.
541#[derive(Debug, Default)]
542pub struct CertificateRegistry {
543    /// Certificates indexed by member public key
544    by_public_key: HashMap<[u8; 32], MembershipCertificate>,
545
546    /// Public key lookup by callsign
547    callsign_to_pubkey: HashMap<String, [u8; 32]>,
548}
549
550impl CertificateRegistry {
551    /// Create a new empty registry.
552    pub fn new() -> Self {
553        Self::default()
554    }
555
556    /// Register a certificate.
557    ///
558    /// Returns the previous certificate if one existed for this public key.
559    pub fn register(&mut self, cert: MembershipCertificate) -> Option<MembershipCertificate> {
560        let pubkey = cert.member_public_key;
561        let callsign = cert.callsign.clone();
562
563        // Remove old callsign mapping if exists
564        if let Some(old_cert) = self.by_public_key.get(&pubkey) {
565            self.callsign_to_pubkey.remove(&old_cert.callsign);
566        }
567
568        // Add new mappings
569        self.callsign_to_pubkey.insert(callsign, pubkey);
570        self.by_public_key.insert(pubkey, cert)
571    }
572
573    /// Get a certificate by member public key.
574    pub fn get_by_pubkey(&self, pubkey: &[u8; 32]) -> Option<&MembershipCertificate> {
575        self.by_public_key.get(pubkey)
576    }
577
578    /// Get a certificate by callsign.
579    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    /// Remove a certificate by public key.
586    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    /// Check if a callsign is already in use.
596    pub fn is_callsign_taken(&self, callsign: &str) -> bool {
597        self.callsign_to_pubkey.contains_key(callsign)
598    }
599
600    /// Get all registered certificates.
601    pub fn certificates(&self) -> impl Iterator<Item = &MembershipCertificate> {
602        self.by_public_key.values()
603    }
604
605    /// Get the number of registered certificates.
606    pub fn len(&self) -> usize {
607        self.by_public_key.len()
608    }
609
610    /// Check if the registry is empty.
611    pub fn is_empty(&self) -> bool {
612        self.by_public_key.is_empty()
613    }
614
615    /// Remove expired certificates.
616    ///
617    /// Returns the number of certificates removed.
618    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; // 24 hours
652
653        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        // Decoded certificate should also verify
721        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; // Expires in 1 second
731        let grace = 500; // 0.5 second grace
732
733        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        // Before expiration
745        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        // At expiration (in grace period)
751        assert!(!cert.is_valid(expires));
752        assert!(cert.is_in_grace_period(expires, grace));
753        assert!(!cert.is_expired(expires, grace));
754
755        // After grace period
756        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        // Certificate claims to be from authority but signed by attacker
770        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(), // Claims authority
778        );
779        cert.sign_with(&attacker); // But signed by attacker
780
781        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        // Tamper with permissions
803        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        // Register certificates
842        assert!(registry.register(cert1.clone()).is_none());
843        assert!(registry.register(cert2.clone()).is_none());
844        assert_eq!(registry.len(), 2);
845
846        // Lookup by public key
847        let found = registry.get_by_pubkey(&member1.public_key_bytes()).unwrap();
848        assert_eq!(found.callsign, "ALPHA-01");
849
850        // Lookup by callsign
851        let found = registry.get_by_callsign("BRAVO-02").unwrap();
852        assert_eq!(found.member_public_key, member2.public_key_bytes());
853
854        // Check callsign taken
855        assert!(registry.is_callsign_taken("ALPHA-01"));
856        assert!(!registry.is_callsign_taken("CHARLIE-03"));
857
858        // Remove
859        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        // cert1: already expired beyond grace
875        let cert1 = MembershipCertificate::new(
876            member1.public_key_bytes(),
877            "EXPIRED-01".to_string(),
878            "A1B2C3D4".to_string(),
879            now - 10000,
880            now - 5000, // Expired 5 seconds ago
881            MemberPermissions::STANDARD,
882            authority.public_key_bytes(),
883        )
884        .signed(&authority);
885
886        // cert2: still valid
887        let cert2 = MembershipCertificate::new(
888            member2.public_key_bytes(),
889            "VALID-02".to_string(),
890            "A1B2C3D4".to_string(),
891            now,
892            now + 10000, // Expires in 10 seconds
893            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        // Remove expired (with 1 second grace)
904        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; // 24 hours
935
936            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            // Convert to token
948            let token = cert.to_token(&authority);
949
950            // Verify token properties
951            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            // Token should be verifiable
958            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            // Create a token (simulating what WearTAK might receive)
971            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            // Convert to certificate
990            let cert = MembershipCertificate::from_token(&token, &authority);
991
992            // Verify certificate properties
993            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            // Certificate should verify
1002            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            // Start with certificate
1014            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            // Convert to token (loses ENROLL permission)
1026            let token = original_cert.to_token(&authority);
1027
1028            // Convert back to certificate (gets STANDARD permissions)
1029            let recovered_cert = MembershipCertificate::from_token(&token, &authority);
1030
1031            // Core fields preserved
1032            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            // Permissions reset to STANDARD (tokens don't carry permissions)
1042            assert_eq!(recovered_cert.permissions, MemberPermissions::STANDARD);
1043
1044            // Both should verify
1045            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            // Certificate with 16-char callsign (max for MembershipCertificate)
1055            let cert = MembershipCertificate::new(
1056                member.public_key_bytes(),
1057                "ALPHA-BRAVO-1234".to_string(), // 16 chars
1058                "A1B2C3D4".to_string(),
1059                1000,
1060                2000,
1061                MemberPermissions::STANDARD,
1062                authority.public_key_bytes(),
1063            )
1064            .signed(&authority);
1065
1066            // Convert to token (max 12 chars)
1067            let token = cert.to_token(&authority);
1068
1069            // Callsign should be truncated
1070            assert_eq!(token.callsign_str(), "ALPHA-BRAVO-");
1071            assert_eq!(token.callsign_str().len(), 12);
1072        }
1073    }
1074}