confium_pki_tc/
abe_and_multitenancy.rs1use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6use std::sync::Mutex;
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct AccessPolicy {
13 pub required_attributes: Vec<String>,
14 pub min_attributes: u32,
15}
16
17#[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
25pub 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 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
56pub 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
66pub 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#[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#[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 #[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 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}