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}