rust_bottle/idcard.rs
1use crate::errors::{BottleError, Result};
2use crate::signing::Sign;
3use rand::RngCore;
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6use std::time::{Duration, SystemTime};
7
8/// An IDCard allows entities to declare sub-keys with specific purposes.
9///
10/// IDCards provide a way to manage multiple keys for an entity, each with
11/// specific purposes (e.g., "sign", "decrypt"). Keys can have expiration
12/// dates, and the IDCard can be signed to establish trust. IDCards also
13/// support metadata and group memberships.
14///
15/// # Example
16///
17/// ```rust
18/// use rust_bottle::*;
19/// use rand::rngs::OsRng;
20/// use std::time::Duration;
21///
22/// let rng = &mut OsRng;
23/// let primary_key = Ed25519Key::generate(rng);
24/// let mut idcard = IDCard::new(&primary_key.public_key_bytes());
25///
26/// idcard.set_metadata("name", "Alice");
27/// idcard.set_key_purposes(&primary_key.public_key_bytes(), &["sign", "decrypt"]);
28/// idcard.set_key_duration(&primary_key.public_key_bytes(), Duration::from_secs(365 * 24 * 3600));
29/// ```
30#[derive(Debug, Clone, Serialize, Deserialize)]
31pub struct IDCard {
32 /// Primary public key for this entity
33 primary_key: Vec<u8>,
34 /// Additional keys with their purposes and metadata, indexed by fingerprint
35 keys: HashMap<Vec<u8>, KeyInfo>,
36 /// Application-specific metadata (key-value pairs)
37 metadata: HashMap<String, String>,
38 /// Serialized group memberships this entity belongs to
39 groups: Vec<Vec<u8>>,
40 /// Cryptographic signature of the IDCard (if signed)
41 signature: Option<Vec<u8>>,
42}
43
44/// Information about a key in an IDCard.
45///
46/// This structure stores the purposes a key is authorized for and its
47/// expiration time, if any.
48#[derive(Debug, Clone, Serialize, Deserialize)]
49struct KeyInfo {
50 /// List of purposes this key is authorized for (e.g., "sign", "decrypt")
51 purposes: Vec<String>,
52 /// Expiration time for this key (None if it doesn't expire)
53 expires_at: Option<SystemTime>,
54}
55
56impl IDCard {
57 /// Create a new IDCard for a public key.
58 ///
59 /// The primary key is automatically added with default purposes "sign"
60 /// and "decrypt" and no expiration.
61 ///
62 /// # Arguments
63 ///
64 /// * `public_key` - The primary public key for this entity
65 ///
66 /// # Returns
67 ///
68 /// A new `IDCard` instance with the primary key registered
69 ///
70 /// # Example
71 ///
72 /// ```rust
73 /// use rust_bottle::*;
74 /// use rand::rngs::OsRng;
75 ///
76 /// let rng = &mut OsRng;
77 /// let key = Ed25519Key::generate(rng);
78 /// let idcard = IDCard::new(&key.public_key_bytes());
79 /// ```
80 pub fn new(public_key: &[u8]) -> Self {
81 let mut keys = HashMap::new();
82 let fingerprint = crate::hash::sha256(public_key);
83 keys.insert(
84 fingerprint,
85 KeyInfo {
86 purposes: vec!["sign".to_string(), "decrypt".to_string()],
87 expires_at: None,
88 },
89 );
90
91 Self {
92 primary_key: public_key.to_vec(),
93 keys,
94 metadata: HashMap::new(),
95 groups: Vec::new(),
96 signature: None,
97 }
98 }
99
100 /// Set metadata key-value pair.
101 ///
102 /// Metadata is application-specific data stored with the IDCard. It is
103 /// not encrypted or signed, so it should not contain sensitive information.
104 ///
105 /// # Arguments
106 ///
107 /// * `key` - Metadata key
108 /// * `value` - Metadata value
109 ///
110 /// # Example
111 ///
112 /// ```rust
113 /// use rust_bottle::*;
114 /// use rand::rngs::OsRng;
115 ///
116 /// let rng = &mut OsRng;
117 /// let key = Ed25519Key::generate(rng);
118 /// let mut idcard = IDCard::new(&key.public_key_bytes());
119 /// idcard.set_metadata("name", "Alice");
120 /// idcard.set_metadata("email", "alice@example.com");
121 /// ```
122 pub fn set_metadata(&mut self, key: &str, value: &str) {
123 self.metadata.insert(key.to_string(), value.to_string());
124 }
125
126 /// Get metadata value by key.
127 ///
128 /// # Arguments
129 ///
130 /// * `key` - Metadata key to look up
131 ///
132 /// # Returns
133 ///
134 /// * `Some(&str)` if the key exists
135 /// * `None` if the key is not found
136 pub fn metadata(&self, key: &str) -> Option<&str> {
137 self.metadata.get(key).map(|s| s.as_str())
138 }
139
140 /// Set the purposes for a key in the IDCard.
141 ///
142 /// Purposes define what operations a key is authorized for. Common
143 /// purposes include "sign" and "decrypt". If the key is not already
144 /// in the IDCard, it will be added.
145 ///
146 /// # Arguments
147 ///
148 /// * `public_key` - The public key to set purposes for
149 /// * `purposes` - Array of purpose strings (e.g., ["sign", "decrypt"])
150 ///
151 /// # Example
152 ///
153 /// ```rust
154 /// use rust_bottle::*;
155 /// use rand::rngs::OsRng;
156 ///
157 /// let rng = &mut OsRng;
158 /// let key = Ed25519Key::generate(rng);
159 /// let mut idcard = IDCard::new(&key.public_key_bytes());
160 /// idcard.set_key_purposes(&key.public_key_bytes(), &["sign"]);
161 /// ```
162 pub fn set_key_purposes(&mut self, public_key: &[u8], purposes: &[&str]) {
163 let fingerprint = crate::hash::sha256(public_key);
164 let key_info = self.keys.entry(fingerprint).or_insert_with(|| KeyInfo {
165 purposes: Vec::new(),
166 expires_at: None,
167 });
168 key_info.purposes = purposes.iter().map(|s| s.to_string()).collect();
169 }
170
171 /// Set the expiration duration for a key.
172 ///
173 /// This sets when the key will expire from now. If the key is not already
174 /// in the IDCard, it will be added with no purposes.
175 ///
176 /// # Arguments
177 ///
178 /// * `public_key` - The public key to set expiration for
179 /// * `duration` - Duration from now until expiration
180 ///
181 /// # Example
182 ///
183 /// ```rust
184 /// use rust_bottle::*;
185 /// use rand::rngs::OsRng;
186 /// use std::time::Duration;
187 ///
188 /// let rng = &mut OsRng;
189 /// let key = Ed25519Key::generate(rng);
190 /// let mut idcard = IDCard::new(&key.public_key_bytes());
191 /// idcard.set_key_duration(&key.public_key_bytes(), Duration::from_secs(365 * 24 * 3600));
192 /// ```
193 pub fn set_key_duration(&mut self, public_key: &[u8], duration: Duration) {
194 let fingerprint = crate::hash::sha256(public_key);
195 let key_info = self.keys.entry(fingerprint).or_insert_with(|| KeyInfo {
196 purposes: Vec::new(),
197 expires_at: None,
198 });
199 key_info.expires_at = Some(SystemTime::now() + duration);
200 }
201
202 /// Test if a key has a specific purpose and is not expired.
203 ///
204 /// This method checks both that the key has the specified purpose and
205 /// that it hasn't expired (if it has an expiration date).
206 ///
207 /// # Arguments
208 ///
209 /// * `public_key` - The public key to test
210 /// * `purpose` - The purpose to check for (e.g., "sign")
211 ///
212 /// # Returns
213 ///
214 /// * `Ok(())` - Key has the purpose and is not expired
215 /// * `Err(BottleError::KeyNotFound)` - Key is not in the IDCard
216 /// * `Err(BottleError::KeyUnfit)` - Key doesn't have the purpose or is expired
217 ///
218 /// # Example
219 ///
220 /// ```rust
221 /// use rust_bottle::*;
222 /// use rand::rngs::OsRng;
223 ///
224 /// let rng = &mut OsRng;
225 /// let key = Ed25519Key::generate(rng);
226 /// let mut idcard = IDCard::new(&key.public_key_bytes());
227 /// idcard.set_key_purposes(&key.public_key_bytes(), &["sign"]);
228 ///
229 /// assert!(idcard.test_key_purpose(&key.public_key_bytes(), "sign").is_ok());
230 /// assert!(idcard.test_key_purpose(&key.public_key_bytes(), "decrypt").is_err());
231 /// ```
232 pub fn test_key_purpose(&self, public_key: &[u8], purpose: &str) -> Result<()> {
233 let fingerprint = crate::hash::sha256(public_key);
234 if let Some(key_info) = self.keys.get(&fingerprint) {
235 // Check expiration
236 if let Some(expires_at) = key_info.expires_at {
237 if SystemTime::now() > expires_at {
238 return Err(BottleError::KeyUnfit);
239 }
240 }
241
242 // Check purpose
243 if key_info.purposes.contains(&purpose.to_string()) {
244 Ok(())
245 } else {
246 Err(BottleError::KeyUnfit)
247 }
248 } else {
249 Err(BottleError::KeyNotFound)
250 }
251 }
252
253 /// Get all key fingerprints that have a specific purpose and are not expired.
254 ///
255 /// # Arguments
256 ///
257 /// * `purpose` - The purpose to filter by (e.g., "sign")
258 ///
259 /// # Returns
260 ///
261 /// A vector of key fingerprints (SHA-256 hashes) that have the specified
262 /// purpose and are not expired
263 ///
264 /// # Example
265 ///
266 /// ```rust
267 /// use rust_bottle::*;
268 /// use rand::rngs::OsRng;
269 ///
270 /// let rng = &mut OsRng;
271 /// let key1 = Ed25519Key::generate(rng);
272 /// let key2 = Ed25519Key::generate(rng);
273 /// let mut idcard = IDCard::new(&key1.public_key_bytes());
274 ///
275 /// idcard.set_key_purposes(&key1.public_key_bytes(), &["sign"]);
276 /// idcard.set_key_purposes(&key2.public_key_bytes(), &["decrypt"]);
277 ///
278 /// let sign_keys = idcard.get_keys("sign");
279 /// assert_eq!(sign_keys.len(), 1);
280 /// ```
281 pub fn get_keys(&self, purpose: &str) -> Vec<Vec<u8>> {
282 self.keys
283 .iter()
284 .filter(|(_, info)| {
285 info.purposes.contains(&purpose.to_string())
286 && info.expires_at.is_none_or(|exp| SystemTime::now() <= exp)
287 })
288 .map(|(fingerprint, _)| fingerprint.clone())
289 .collect()
290 }
291
292 /// Update the list of group memberships.
293 ///
294 /// Groups are stored as serialized membership data. This replaces the
295 /// entire list of groups.
296 ///
297 /// # Arguments
298 ///
299 /// * `groups` - Vector of serialized membership data
300 pub fn update_groups(&mut self, groups: Vec<Vec<u8>>) {
301 self.groups = groups;
302 }
303
304 /// Sign the IDCard with a private key.
305 ///
306 /// This creates a cryptographic signature of the IDCard (excluding the
307 /// signature field itself) and stores it. The signed IDCard is then
308 /// serialized and returned.
309 ///
310 /// # Arguments
311 ///
312 /// * `rng` - A random number generator
313 /// * `signer` - A signer implementing the `Sign` trait
314 ///
315 /// # Returns
316 ///
317 /// * `Ok(Vec<u8>)` - Serialized signed IDCard
318 /// * `Err(BottleError::Serialization)` - If serialization fails
319 /// * `Err(BottleError::VerifyFailed)` - If signing fails
320 ///
321 /// # Example
322 ///
323 /// ```rust
324 /// use rust_bottle::*;
325 /// use rand::rngs::OsRng;
326 ///
327 /// let rng = &mut OsRng;
328 /// let key = Ed25519Key::generate(rng);
329 /// let mut idcard = IDCard::new(&key.public_key_bytes());
330 ///
331 /// let signed_bytes = idcard.sign(rng, &key).unwrap();
332 /// ```
333 pub fn sign<R: RngCore>(&mut self, rng: &mut R, signer: &dyn Sign) -> Result<Vec<u8>> {
334 // Create data to sign (everything except signature)
335 let data_to_sign = self.create_signing_data()?;
336 let signature = signer.sign(rng, &data_to_sign)?;
337 self.signature = Some(signature.clone());
338
339 // Serialize signed IDCard
340 self.to_bytes()
341 }
342
343 /// Create data to sign (everything except the signature field).
344 ///
345 /// This serializes the IDCard with the signature field set to None,
346 /// which is what gets signed.
347 ///
348 /// # Returns
349 ///
350 /// Serialized IDCard bytes without the signature
351 fn create_signing_data(&self) -> Result<Vec<u8>> {
352 // Serialize everything except signature
353 let mut card = self.clone();
354 card.signature = None;
355 bincode::serialize(&card)
356 .map_err(|e| BottleError::Serialization(format!("Failed to serialize IDCard: {}", e)))
357 }
358
359 /// Serialize the IDCard to bytes using bincode.
360 ///
361 /// # Returns
362 ///
363 /// * `Ok(Vec<u8>)` - Serialized IDCard bytes
364 /// * `Err(BottleError::Serialization)` - If serialization fails
365 ///
366 /// # Example
367 ///
368 /// ```rust
369 /// use rust_bottle::*;
370 /// use rand::rngs::OsRng;
371 ///
372 /// let rng = &mut OsRng;
373 /// let key = Ed25519Key::generate(rng);
374 /// let idcard = IDCard::new(&key.public_key_bytes());
375 ///
376 /// let bytes = idcard.to_bytes().unwrap();
377 /// let restored = IDCard::from_bytes(&bytes).unwrap();
378 /// ```
379 pub fn to_bytes(&self) -> Result<Vec<u8>> {
380 bincode::serialize(self)
381 .map_err(|e| BottleError::Serialization(format!("Failed to serialize IDCard: {}", e)))
382 }
383
384 /// Deserialize an IDCard from bytes.
385 ///
386 /// # Arguments
387 ///
388 /// * `data` - Serialized IDCard bytes (from `to_bytes`)
389 ///
390 /// # Returns
391 ///
392 /// * `Ok(IDCard)` - Deserialized IDCard
393 /// * `Err(BottleError::Deserialization)` - If deserialization fails
394 ///
395 /// # Example
396 ///
397 /// ```rust
398 /// use rust_bottle::*;
399 /// use rand::rngs::OsRng;
400 ///
401 /// let rng = &mut OsRng;
402 /// let key = Ed25519Key::generate(rng);
403 /// let idcard = IDCard::new(&key.public_key_bytes());
404 ///
405 /// let bytes = idcard.to_bytes().unwrap();
406 /// let restored = IDCard::from_bytes(&bytes).unwrap();
407 /// ```
408 pub fn from_bytes(data: &[u8]) -> Result<Self> {
409 bincode::deserialize(data).map_err(|e| {
410 BottleError::Deserialization(format!("Failed to deserialize IDCard: {}", e))
411 })
412 }
413
414 /// Unmarshal from binary (alias for `from_bytes`).
415 ///
416 /// This is provided for compatibility with gobottle's API.
417 ///
418 /// # Arguments
419 ///
420 /// * `data` - Serialized IDCard bytes
421 ///
422 /// # Returns
423 ///
424 /// * `Ok(IDCard)` - Deserialized IDCard
425 /// * `Err(BottleError::Deserialization)` - If deserialization fails
426 pub fn unmarshal_binary(data: &[u8]) -> Result<Self> {
427 Self::from_bytes(data)
428 }
429}