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().into())))
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().into())))
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    /// Select exactly the configured slot, requiring a present token.
119    pub fn find_by_id(context: &Pkcs11Context, id: u64) -> HsmResult<Self> {
120        let slot = Slot::try_from(id).map_err(|e| HsmError::SlotAccess(e.to_string()))?;
121        if !Self::enumerate_slots_with_tokens(context)?.contains(&slot) {
122            return Err(HsmError::SlotAccess(format!(
123                "configured slot {id} has no token"
124            )));
125        }
126        Ok(Self::new(context.clone(), slot))
127    }
128
129    /// Find the first slot with a token.
130    ///
131    /// # Arguments
132    ///
133    /// * `context` - PKCS#11 library context
134    pub fn find_first_slot(context: &Pkcs11Context) -> HsmResult<Self> {
135        let slots = Self::enumerate_slots_with_tokens(context)?;
136
137        let slot = slots
138            .into_iter()
139            .next()
140            .ok_or_else(|| HsmError::SlotAccess("No slots with tokens found".to_string()))?;
141
142        Ok(Self::new(context.clone(), slot))
143    }
144
145    /// Find a slot by token label.
146    ///
147    /// # Arguments
148    ///
149    /// * `context` - PKCS#11 library context
150    /// * `label` - Token label to search for
151    pub fn find_by_label(context: &Pkcs11Context, label: &str) -> HsmResult<Self> {
152        let slots = Self::enumerate_slots_with_tokens(context)?;
153
154        for slot_id in slots {
155            let slot = Self::new(context.clone(), slot_id);
156            if let Ok(token_label) = slot.token_label()
157                && token_label == label
158            {
159                return Ok(slot);
160            }
161        }
162
163        Err(HsmError::SlotAccess(format!(
164            "No slot found with token label '{label}'"
165        )))
166    }
167}
168
169#[cfg(test)]
170mod tests {
171
172    #[test]
173    #[ignore = "requires HSM hardware"]
174    fn test_slot_enumeration() {
175        // Would require a real PKCS#11 setup
176    }
177}