⚠️ VeridianOS Kernel Documentation - This is low-level kernel code. All functions are unsafe unless explicitly marked otherwise. no_std

veridian_kernel/services/mesh/
identity.rs

1//! Service Identity (SPIFFE)
2//!
3//! Provides SPIFFE-based identity management for service mesh
4//! authentication including certificate issuance, verification,
5//! and rotation.
6
7#![allow(dead_code)]
8
9use alloc::{collections::BTreeMap, string::String, vec::Vec};
10
11// ---------------------------------------------------------------------------
12// SPIFFE ID
13// ---------------------------------------------------------------------------
14
15/// A SPIFFE identity (spiffe://trust-domain/path).
16#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
17pub struct SpiffeId {
18    /// Trust domain (e.g., "cluster.local").
19    pub trust_domain: String,
20    /// Path (e.g., "/ns/default/sa/nginx").
21    pub path: String,
22}
23
24impl SpiffeId {
25    /// Create a new SPIFFE ID.
26    pub fn new(trust_domain: String, path: String) -> Self {
27        SpiffeId { trust_domain, path }
28    }
29
30    /// Create from Kubernetes-style components.
31    pub fn from_k8s(trust_domain: &str, namespace: &str, service_account: &str) -> Self {
32        SpiffeId {
33            trust_domain: String::from(trust_domain),
34            path: alloc::format!("/ns/{}/sa/{}", namespace, service_account),
35        }
36    }
37
38    /// Get the full SPIFFE URI.
39    pub fn uri(&self) -> String {
40        alloc::format!("spiffe://{}{}", self.trust_domain, self.path)
41    }
42
43    /// Parse a SPIFFE URI string.
44    pub fn parse(uri: &str) -> Option<Self> {
45        let stripped = uri.strip_prefix("spiffe://")?;
46        let slash_pos = stripped.find('/')?;
47        let trust_domain = String::from(&stripped[..slash_pos]);
48        let path = String::from(&stripped[slash_pos..]);
49        Some(SpiffeId { trust_domain, path })
50    }
51}
52
53// ---------------------------------------------------------------------------
54// Service Identity
55// ---------------------------------------------------------------------------
56
57/// A service identity with associated certificate material.
58#[derive(Debug, Clone)]
59pub struct ServiceIdentity {
60    /// SPIFFE ID.
61    pub spiffe_id: SpiffeId,
62    /// X.509 certificate data (DER-encoded stub).
63    pub certificate_data: Vec<u8>,
64    /// Private key data (DER-encoded stub).
65    pub private_key_data: Vec<u8>,
66    /// Tick when the certificate expires.
67    pub expiry_tick: u64,
68    /// Tick when the certificate was issued.
69    pub issued_tick: u64,
70    /// Certificate serial number.
71    pub serial: u64,
72}
73
74impl ServiceIdentity {
75    /// Check if the certificate has expired.
76    pub fn is_expired(&self, current_tick: u64) -> bool {
77        current_tick >= self.expiry_tick
78    }
79
80    /// Check if the certificate should be rotated (within 20% of expiry).
81    pub fn needs_rotation(&self, current_tick: u64) -> bool {
82        let lifetime = self.expiry_tick.saturating_sub(self.issued_tick);
83        let threshold = self.expiry_tick.saturating_sub(lifetime / 5);
84        current_tick >= threshold
85    }
86}
87
88// ---------------------------------------------------------------------------
89// Identity Error
90// ---------------------------------------------------------------------------
91
92/// Identity provider error.
93#[derive(Debug, Clone, PartialEq, Eq)]
94pub enum IdentityError {
95    /// Identity not found.
96    NotFound(String),
97    /// Identity already exists.
98    AlreadyExists(String),
99    /// Certificate expired.
100    CertificateExpired(String),
101    /// Invalid SPIFFE URI.
102    InvalidSpiffeUri(String),
103    /// CA error.
104    CaError(String),
105}
106
107// ---------------------------------------------------------------------------
108// Identity Provider
109// ---------------------------------------------------------------------------
110
111/// Service identity provider with self-signed CA stub.
112#[derive(Debug)]
113pub struct IdentityProvider {
114    /// Issued identities keyed by SPIFFE URI.
115    identities: BTreeMap<String, ServiceIdentity>,
116    /// Trust domain.
117    trust_domain: String,
118    /// CA certificate data (self-signed stub).
119    ca_certificate: Vec<u8>,
120    /// CA private key data (stub).
121    ca_private_key: Vec<u8>,
122    /// Next serial number.
123    next_serial: u64,
124    /// Default certificate lifetime in ticks.
125    cert_lifetime: u64,
126}
127
128impl Default for IdentityProvider {
129    fn default() -> Self {
130        Self::new(String::from("cluster.local"))
131    }
132}
133
134impl IdentityProvider {
135    /// Default certificate lifetime: 3600 ticks (1 hour at 1 tick/sec).
136    pub const DEFAULT_CERT_LIFETIME: u64 = 3600;
137
138    /// Create a new identity provider.
139    pub fn new(trust_domain: String) -> Self {
140        // Generate deterministic CA material (stub)
141        let mut ca_cert = Vec::with_capacity(32);
142        for (i, b) in trust_domain.bytes().enumerate() {
143            ca_cert.push(b.wrapping_add(i as u8));
144        }
145        let ca_key = ca_cert.clone();
146
147        IdentityProvider {
148            identities: BTreeMap::new(),
149            trust_domain,
150            ca_certificate: ca_cert,
151            ca_private_key: ca_key,
152            next_serial: 1,
153            cert_lifetime: Self::DEFAULT_CERT_LIFETIME,
154        }
155    }
156
157    /// Issue an identity for a service.
158    pub fn issue_identity(
159        &mut self,
160        spiffe_id: SpiffeId,
161        current_tick: u64,
162    ) -> Result<&ServiceIdentity, IdentityError> {
163        let uri = spiffe_id.uri();
164        if self.identities.contains_key(&uri) {
165            return Err(IdentityError::AlreadyExists(uri));
166        }
167
168        let serial = self.next_serial;
169        self.next_serial += 1;
170
171        // Generate deterministic cert/key (stub)
172        let mut cert_data = Vec::with_capacity(64);
173        cert_data.extend_from_slice(&serial.to_le_bytes());
174        cert_data.extend_from_slice(&current_tick.to_le_bytes());
175        for b in uri.bytes() {
176            cert_data.push(b);
177        }
178
179        let key_data = cert_data.iter().map(|b| b.wrapping_add(0x42)).collect();
180
181        let identity = ServiceIdentity {
182            spiffe_id,
183            certificate_data: cert_data,
184            private_key_data: key_data,
185            expiry_tick: current_tick + self.cert_lifetime,
186            issued_tick: current_tick,
187            serial,
188        };
189
190        self.identities.insert(uri.clone(), identity);
191        Ok(self.identities.get(&uri).unwrap())
192    }
193
194    /// Verify an identity's certificate is valid.
195    pub fn verify_identity(
196        &self,
197        spiffe_uri: &str,
198        current_tick: u64,
199    ) -> Result<bool, IdentityError> {
200        let identity = self
201            .identities
202            .get(spiffe_uri)
203            .ok_or_else(|| IdentityError::NotFound(String::from(spiffe_uri)))?;
204
205        if identity.is_expired(current_tick) {
206            return Err(IdentityError::CertificateExpired(String::from(spiffe_uri)));
207        }
208
209        Ok(true)
210    }
211
212    /// Rotate a certificate (renew before expiry).
213    pub fn rotate_certificate(
214        &mut self,
215        spiffe_uri: &str,
216        current_tick: u64,
217    ) -> Result<&ServiceIdentity, IdentityError> {
218        let identity = self
219            .identities
220            .get(spiffe_uri)
221            .ok_or_else(|| IdentityError::NotFound(String::from(spiffe_uri)))?;
222
223        let spiffe_id = identity.spiffe_id.clone();
224
225        // Remove old and reissue
226        self.identities.remove(spiffe_uri);
227        self.issue_identity(spiffe_id, current_tick)
228    }
229
230    /// Get an identity by SPIFFE URI.
231    pub fn get_identity(&self, spiffe_uri: &str) -> Option<&ServiceIdentity> {
232        self.identities.get(spiffe_uri)
233    }
234
235    /// List all identities.
236    pub fn list_identities(&self) -> Vec<&ServiceIdentity> {
237        self.identities.values().collect()
238    }
239
240    /// Get the CA certificate.
241    pub fn ca_certificate(&self) -> &[u8] {
242        &self.ca_certificate
243    }
244
245    /// Get the trust domain.
246    pub fn trust_domain(&self) -> &str {
247        &self.trust_domain
248    }
249
250    /// Get the total number of issued identities.
251    pub fn identity_count(&self) -> usize {
252        self.identities.len()
253    }
254}
255
256// ---------------------------------------------------------------------------
257// Tests
258// ---------------------------------------------------------------------------
259
260#[cfg(test)]
261mod tests {
262    #[allow(unused_imports)]
263    use alloc::string::ToString;
264
265    use super::*;
266
267    fn make_provider() -> IdentityProvider {
268        IdentityProvider::new(String::from("cluster.local"))
269    }
270
271    fn test_spiffe() -> SpiffeId {
272        SpiffeId::from_k8s("cluster.local", "default", "nginx")
273    }
274
275    #[test]
276    fn test_spiffe_uri() {
277        let id = test_spiffe();
278        assert_eq!(id.uri(), "spiffe://cluster.local/ns/default/sa/nginx");
279    }
280
281    #[test]
282    fn test_spiffe_parse() {
283        let uri = "spiffe://cluster.local/ns/default/sa/app";
284        let id = SpiffeId::parse(uri).unwrap();
285        assert_eq!(id.trust_domain, "cluster.local");
286        assert_eq!(id.path, "/ns/default/sa/app");
287    }
288
289    #[test]
290    fn test_issue_identity() {
291        let mut provider = make_provider();
292        let identity = provider.issue_identity(test_spiffe(), 100).unwrap();
293        assert_eq!(identity.issued_tick, 100);
294        assert!(!identity.certificate_data.is_empty());
295        assert_eq!(provider.identity_count(), 1);
296    }
297
298    #[test]
299    fn test_issue_duplicate() {
300        let mut provider = make_provider();
301        provider.issue_identity(test_spiffe(), 100).unwrap();
302        assert!(provider.issue_identity(test_spiffe(), 200).is_err());
303    }
304
305    #[test]
306    fn test_verify_identity() {
307        let mut provider = make_provider();
308        let id = test_spiffe();
309        let uri = id.uri();
310        provider.issue_identity(id, 100).unwrap();
311        assert!(provider.verify_identity(&uri, 200).unwrap());
312    }
313
314    #[test]
315    fn test_verify_expired() {
316        let mut provider = make_provider();
317        let id = test_spiffe();
318        let uri = id.uri();
319        provider.issue_identity(id, 100).unwrap();
320        // Expired: 100 + 3600 = 3700
321        assert!(provider.verify_identity(&uri, 4000).is_err());
322    }
323
324    #[test]
325    fn test_rotate_certificate() {
326        let mut provider = make_provider();
327        let id = test_spiffe();
328        let uri = id.uri();
329        provider.issue_identity(id, 100).unwrap();
330        let serial_before = provider.get_identity(&uri).unwrap().serial;
331
332        let rotated = provider.rotate_certificate(&uri, 3000).unwrap();
333        assert!(rotated.serial > serial_before);
334        assert_eq!(rotated.issued_tick, 3000);
335    }
336
337    #[test]
338    fn test_needs_rotation() {
339        let identity = ServiceIdentity {
340            spiffe_id: test_spiffe(),
341            certificate_data: Vec::new(),
342            private_key_data: Vec::new(),
343            expiry_tick: 1000,
344            issued_tick: 0,
345            serial: 1,
346        };
347        // 80% through lifetime -> needs rotation
348        assert!(identity.needs_rotation(850));
349        // 50% through -> not yet
350        assert!(!identity.needs_rotation(500));
351    }
352}