Skip to main content

kipuka_hsm/
slot.rs

1//! HSM slot and session management.
2
3use crate::error::{HsmError, HsmResult};
4use crate::pkcs11::Pkcs11Context;
5use cryptoki::session::{Session, UserType};
6use cryptoki::slot::Slot;
7use cryptoki::types::AuthPin;
8
9/// HSM slot with session management.
10pub struct HsmSlot {
11    context: Pkcs11Context,
12    slot: Slot,
13}
14
15impl HsmSlot {
16    /// Create a new HSM slot.
17    ///
18    /// # Arguments
19    ///
20    /// * `context` - PKCS#11 library context
21    /// * `slot` - Slot identifier
22    pub fn new(context: Pkcs11Context, slot: Slot) -> Self {
23        Self { context, slot }
24    }
25
26    /// Get the slot identifier.
27    pub fn slot(&self) -> Slot {
28        self.slot
29    }
30
31    /// Get slot information description.
32    pub fn slot_info(&self) -> HsmResult<String> {
33        self.context.with_pkcs11(|pkcs11| {
34            let info = pkcs11.get_slot_info(self.slot)?;
35            Ok(format!(
36                "Slot: {} ({})",
37                info.slot_description(),
38                if info.token_present() {
39                    "token present"
40                } else {
41                    "no token"
42                }
43            ))
44        })
45    }
46
47    /// Get token information description.
48    pub fn token_info(&self) -> HsmResult<String> {
49        self.context.with_pkcs11(|pkcs11| {
50            let info = pkcs11.get_token_info(self.slot)?;
51            Ok(format!("Token: {}", info.label()))
52        })
53    }
54
55    /// Get token label.
56    pub fn token_label(&self) -> HsmResult<String> {
57        self.context.with_pkcs11(|pkcs11| {
58            let info = pkcs11.get_token_info(self.slot)?;
59            Ok(info.label().trim().to_string())
60        })
61    }
62
63    /// Open a read-only session.
64    pub fn open_ro_session(&self) -> HsmResult<Session> {
65        self.context.with_pkcs11(|pkcs11| {
66            pkcs11
67                .open_ro_session(self.slot)
68                .map_err(|e| HsmError::SessionCreate(format!("Failed to open RO session: {e}")))
69        })
70    }
71
72    /// Open a read-write session.
73    pub fn open_rw_session(&self) -> HsmResult<Session> {
74        self.context.with_pkcs11(|pkcs11| {
75            pkcs11
76                .open_rw_session(self.slot)
77                .map_err(|e| HsmError::SessionCreate(format!("Failed to open RW session: {e}")))
78        })
79    }
80
81    /// Login to the slot as a user.
82    ///
83    /// # Arguments
84    ///
85    /// * `session` - Active session
86    /// * `pin` - User PIN
87    pub fn login(&self, session: &Session, pin: &str) -> HsmResult<()> {
88        session
89            .login(UserType::User, Some(&AuthPin::new(pin.to_owned())))
90            .map_err(|e| HsmError::Login(format!("User login failed: {e}")))
91    }
92
93    /// Login as security officer (SO).
94    ///
95    /// # Arguments
96    ///
97    /// * `session` - Active session
98    /// * `pin` - SO PIN
99    pub fn login_so(&self, session: &Session, pin: &str) -> HsmResult<()> {
100        session
101            .login(UserType::So, Some(&AuthPin::new(pin.to_owned())))
102            .map_err(|e| HsmError::Login(format!("SO login failed: {e}")))
103    }
104
105    /// Enumerate all slots with tokens present.
106    ///
107    /// # Arguments
108    ///
109    /// * `context` - PKCS#11 library context
110    pub fn enumerate_slots_with_tokens(context: &Pkcs11Context) -> HsmResult<Vec<Slot>> {
111        context.with_pkcs11(|pkcs11| {
112            pkcs11
113                .get_slots_with_token()
114                .map_err(|e| HsmError::SlotAccess(format!("Failed to enumerate slots: {e}")))
115        })
116    }
117
118    /// Find the first slot with a token.
119    ///
120    /// # Arguments
121    ///
122    /// * `context` - PKCS#11 library context
123    pub fn find_first_slot(context: &Pkcs11Context) -> HsmResult<Self> {
124        let slots = Self::enumerate_slots_with_tokens(context)?;
125
126        let slot = slots
127            .into_iter()
128            .next()
129            .ok_or_else(|| HsmError::SlotAccess("No slots with tokens found".to_string()))?;
130
131        Ok(Self::new(context.clone(), slot))
132    }
133
134    /// Find a slot by token label.
135    ///
136    /// # Arguments
137    ///
138    /// * `context` - PKCS#11 library context
139    /// * `label` - Token label to search for
140    pub fn find_by_label(context: &Pkcs11Context, label: &str) -> HsmResult<Self> {
141        let slots = Self::enumerate_slots_with_tokens(context)?;
142
143        for slot_id in slots {
144            let slot = Self::new(context.clone(), slot_id);
145            if let Ok(token_label) = slot.token_label()
146                && token_label == label
147            {
148                return Ok(slot);
149            }
150        }
151
152        Err(HsmError::SlotAccess(format!(
153            "No slot found with token label '{label}'"
154        )))
155    }
156}
157
158#[cfg(test)]
159mod tests {
160    use super::*;
161
162    #[test]
163    #[ignore = "requires HSM hardware"]
164    fn test_slot_enumeration() {
165        // Would require a real PKCS#11 setup
166    }
167}