Skip to main content

confium_pki_tc/
abe_and_multitenancy.rs

1//! Attribute-based encryption — encrypt to attributes, threshold decrypt.
2//! Multi-tenancy isolation — per-quorum isolation.
3
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6use std::sync::Mutex;
7
8// === Attribute-Based Encryption ===
9
10/// An access policy for decryption.
11#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct AccessPolicy {
13    pub required_attributes: Vec<String>,
14    pub min_attributes: u32,
15}
16
17/// An ABE ciphertext.
18#[derive(Debug, Clone, Serialize, Deserialize)]
19pub struct AbeCiphertext {
20    pub policy: AccessPolicy,
21    pub encrypted_data_hex: String,
22    pub attribute_keys_hex: HashMap<String, String>,
23}
24
25/// Encrypt data under an access policy.
26pub fn encrypt(policy: AccessPolicy, data: &[u8]) -> AbeCiphertext {
27    use sha2::{Digest, Sha256};
28    let mut attribute_keys = HashMap::new();
29    for attr in &policy.required_attributes {
30        let mut h = Sha256::new();
31        h.update(b"abe-key");
32        h.update(attr.as_bytes());
33        attribute_keys.insert(attr.clone(), hex::encode(h.finalize()));
34    }
35    // Simplified: XOR with hash of concatenated attribute keys
36    let mut key_material = Vec::new();
37    for attr in &policy.required_attributes {
38        key_material.extend_from_slice(attr.as_bytes());
39    }
40    let mut h = Sha256::new();
41    h.update(b"abe-encrypt");
42    h.update(&key_material);
43    let key = h.finalize();
44    let encrypted: Vec<u8> = data
45        .iter()
46        .enumerate()
47        .map(|(i, &b)| b ^ key[i % key.len()])
48        .collect();
49    AbeCiphertext {
50        policy,
51        encrypted_data_hex: hex::encode(&encrypted),
52        attribute_keys_hex: attribute_keys,
53    }
54}
55
56/// Check if a set of attributes satisfies the policy.
57pub fn satisfies(attributes: &[String], policy: &AccessPolicy) -> bool {
58    let matching = policy
59        .required_attributes
60        .iter()
61        .filter(|req| attributes.contains(req))
62        .count();
63    matching >= policy.min_attributes as usize
64}
65
66/// Decrypt with a set of attributes.
67pub fn decrypt(ciphertext: &AbeCiphertext, attributes: &[String]) -> Option<Vec<u8>> {
68    if !satisfies(attributes, &ciphertext.policy) {
69        return None;
70    }
71    use sha2::{Digest, Sha256};
72    let mut key_material = Vec::new();
73    for attr in &ciphertext.policy.required_attributes {
74        key_material.extend_from_slice(attr.as_bytes());
75    }
76    let mut h = Sha256::new();
77    h.update(b"abe-encrypt");
78    h.update(&key_material);
79    let key = h.finalize();
80    let encrypted = hex::decode(&ciphertext.encrypted_data_hex).ok()?;
81    let decrypted: Vec<u8> = encrypted
82        .iter()
83        .enumerate()
84        .map(|(i, &b)| b ^ key[i % key.len()])
85        .collect();
86    Some(decrypted)
87}
88
89// === Multi-Tenancy Isolation ===
90
91/// A tenant (quorum) with isolated resources.
92#[derive(Debug, Clone)]
93pub struct Tenant {
94    pub quorum_id: String,
95    pub max_sessions: usize,
96    pub active_sessions: usize,
97    pub rate_limit_per_minute: u32,
98    pub allowed_schemes: Vec<String>,
99}
100
101/// Multi-tenant manager.
102#[derive(Default)]
103pub struct TenantManager {
104    tenants: Mutex<HashMap<String, Tenant>>,
105}
106
107impl TenantManager {
108    pub fn new() -> Self {
109        Self::default()
110    }
111
112    pub fn register(&self, tenant: Tenant) {
113        self.tenants
114            .lock()
115            .unwrap()
116            .insert(tenant.quorum_id.clone(), tenant);
117    }
118
119    pub fn get(&self, quorum_id: &str) -> Option<Tenant> {
120        self.tenants.lock().unwrap().get(quorum_id).cloned()
121    }
122
123    pub fn can_create_session(&self, quorum_id: &str) -> bool {
124        self.tenants
125            .lock()
126            .unwrap()
127            .get(quorum_id)
128            .map(|t| t.active_sessions < t.max_sessions)
129            .unwrap_or(false)
130    }
131
132    pub fn increment_sessions(&self, quorum_id: &str) -> bool {
133        let mut tenants = self.tenants.lock().unwrap();
134        if let Some(t) = tenants.get_mut(quorum_id) {
135            if t.active_sessions >= t.max_sessions {
136                return false;
137            }
138            t.active_sessions += 1;
139            return true;
140        }
141        false
142    }
143
144    pub fn decrement_sessions(&self, quorum_id: &str) {
145        let mut tenants = self.tenants.lock().unwrap();
146        if let Some(t) = tenants.get_mut(quorum_id) {
147            t.active_sessions = t.active_sessions.saturating_sub(1);
148        }
149    }
150
151    pub fn is_scheme_allowed(&self, quorum_id: &str, scheme: &str) -> bool {
152        self.tenants
153            .lock()
154            .unwrap()
155            .get(quorum_id)
156            .map(|t| t.allowed_schemes.iter().any(|s| s == scheme))
157            .unwrap_or(false)
158    }
159
160    pub fn tenant_count(&self) -> usize {
161        self.tenants.lock().unwrap().len()
162    }
163
164    pub fn remove(&self, quorum_id: &str) {
165        self.tenants.lock().unwrap().remove(quorum_id);
166    }
167}
168
169#[cfg(test)]
170mod tests {
171    use super::*;
172
173    // ABE tests
174
175    #[test]
176    fn abe_encrypt_decrypt_with_attributes() {
177        let policy = AccessPolicy {
178            required_attributes: vec!["region:eu".into(), "role:director".into()],
179            min_attributes: 2,
180        };
181        let data = b"secret data";
182        let ct = encrypt(policy, data);
183        let pt = decrypt(&ct, &["region:eu".into(), "role:director".into()]).unwrap();
184        assert_eq!(pt, data);
185    }
186
187    #[test]
188    fn abe_insufficient_attributes_rejected() {
189        let policy = AccessPolicy {
190            required_attributes: vec!["a".into(), "b".into()],
191            min_attributes: 2,
192        };
193        let ct = encrypt(policy, b"data");
194        assert!(decrypt(&ct, &["a".into()]).is_none());
195    }
196
197    #[test]
198    fn abe_partial_satisfy() {
199        let policy = AccessPolicy {
200            required_attributes: vec!["a".into(), "b".into(), "c".into()],
201            min_attributes: 2,
202        };
203        let ct = encrypt(policy, b"data");
204        assert!(decrypt(&ct, &["a".into(), "b".into()]).is_some());
205    }
206
207    // Multi-tenancy tests
208
209    fn make_tenant(quorum: &str) -> Tenant {
210        Tenant {
211            quorum_id: quorum.into(),
212            max_sessions: 5,
213            active_sessions: 0,
214            rate_limit_per_minute: 100,
215            allowed_schemes: vec!["CMP20".into(), "FROST-P256".into()],
216        }
217    }
218
219    #[test]
220    fn register_and_get_tenant() {
221        let mgr = TenantManager::new();
222        mgr.register(make_tenant("q1"));
223        assert!(mgr.get("q1").is_some());
224        assert!(mgr.get("q2").is_none());
225    }
226
227    #[test]
228    fn session_limit_enforced() {
229        let mgr = TenantManager::new();
230        mgr.register(make_tenant("q1"));
231        for _ in 0..5 {
232            assert!(mgr.increment_sessions("q1"));
233        }
234        assert!(!mgr.increment_sessions("q1"));
235    }
236
237    #[test]
238    fn decrement_sessions() {
239        let mgr = TenantManager::new();
240        mgr.register(make_tenant("q1"));
241        mgr.increment_sessions("q1");
242        mgr.increment_sessions("q1");
243        mgr.decrement_sessions("q1");
244        let t = mgr.get("q1").unwrap();
245        assert_eq!(t.active_sessions, 1);
246    }
247
248    #[test]
249    fn scheme_allowed_check() {
250        let mgr = TenantManager::new();
251        mgr.register(make_tenant("q1"));
252        assert!(mgr.is_scheme_allowed("q1", "CMP20"));
253        assert!(!mgr.is_scheme_allowed("q1", "RSA"));
254    }
255
256    #[test]
257    fn unknown_tenant_denied() {
258        let mgr = TenantManager::new();
259        assert!(!mgr.can_create_session("unknown"));
260        assert!(!mgr.increment_sessions("unknown"));
261    }
262
263    #[test]
264    fn remove_tenant() {
265        let mgr = TenantManager::new();
266        mgr.register(make_tenant("q1"));
267        assert_eq!(mgr.tenant_count(), 1);
268        mgr.remove("q1");
269        assert_eq!(mgr.tenant_count(), 0);
270    }
271}