manager.rs (17301B)
1 /* -*- Mode: rust; rust-indent-offset: 4 -*- */ 2 /* This Source Code Form is subject to the terms of the Mozilla Public 3 * License, v. 2.0. If a copy of the MPL was not distributed with this 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 6 use pkcs11_bindings::*; 7 use rsclientcerts_util::error::{Error, ErrorType}; 8 use rsclientcerts_util::error_here; 9 use std::collections::{BTreeMap, BTreeSet}; 10 use std::convert::TryInto; 11 use std::marker::PhantomData; 12 13 use crate::cryptoki::CryptokiCert; 14 15 pub trait CryptokiObject { 16 fn matches(&self, attrs: &[(CK_ATTRIBUTE_TYPE, Vec<u8>)]) -> bool; 17 fn get_attribute(&self, attribute: CK_ATTRIBUTE_TYPE) -> Option<&[u8]>; 18 } 19 20 pub trait Sign { 21 fn get_signature_length( 22 &mut self, 23 data: &[u8], 24 params: &Option<CK_RSA_PKCS_PSS_PARAMS>, 25 ) -> Result<usize, Error>; 26 fn sign( 27 &mut self, 28 data: &[u8], 29 params: &Option<CK_RSA_PKCS_PSS_PARAMS>, 30 ) -> Result<Vec<u8>, Error>; 31 } 32 33 pub trait ClientCertsBackend { 34 type Key: CryptokiObject + Sign; 35 36 #[allow(clippy::type_complexity)] 37 fn find_objects(&mut self) -> Result<(Vec<CryptokiCert>, Vec<Self::Key>), Error>; 38 fn get_slot_info(&self) -> CK_SLOT_INFO; 39 fn get_token_info(&self) -> CK_TOKEN_INFO; 40 fn get_mechanism_list(&self) -> Vec<CK_MECHANISM_TYPE>; 41 fn login(&mut self) {} 42 fn logout(&mut self) {} 43 fn is_logged_in(&self) -> bool { 44 false 45 } 46 } 47 48 pub trait IsSearchingForClientCerts { 49 fn is_searching_for_client_certs() -> bool; 50 } 51 52 const SUPPORTED_ATTRIBUTES: &[CK_ATTRIBUTE_TYPE] = &[ 53 CKA_CLASS, 54 CKA_TOKEN, 55 CKA_LABEL, 56 CKA_ID, 57 CKA_VALUE, 58 CKA_ISSUER, 59 CKA_SERIAL_NUMBER, 60 CKA_SUBJECT, 61 CKA_PRIVATE, 62 CKA_KEY_TYPE, 63 CKA_MODULUS, 64 CKA_EC_PARAMS, 65 ]; 66 67 enum Object<B: ClientCertsBackend> { 68 Cert(CryptokiCert), 69 Key(B::Key), 70 } 71 72 impl<B: ClientCertsBackend> Object<B> { 73 fn matches(&self, attrs: &[(CK_ATTRIBUTE_TYPE, Vec<u8>)]) -> bool { 74 match self { 75 Object::Cert(cert) => cert.matches(attrs), 76 Object::Key(key) => key.matches(attrs), 77 } 78 } 79 80 fn get_attribute(&self, attribute: CK_ATTRIBUTE_TYPE) -> Option<&[u8]> { 81 match self { 82 Object::Cert(cert) => cert.get_attribute(attribute), 83 Object::Key(key) => key.get_attribute(attribute), 84 } 85 } 86 87 fn id(&self) -> Result<&[u8], Error> { 88 self.get_attribute(CKA_ID) 89 .ok_or_else(|| error_here!(ErrorType::LibraryFailure)) 90 } 91 92 fn get_signature_length( 93 &mut self, 94 data: Vec<u8>, 95 params: &Option<CK_RSA_PKCS_PSS_PARAMS>, 96 ) -> Result<usize, Error> { 97 match self { 98 Object::Cert(_) => Err(error_here!(ErrorType::InvalidArgument)), 99 Object::Key(key) => key.get_signature_length(&data, params), 100 } 101 } 102 103 fn sign( 104 &mut self, 105 data: Vec<u8>, 106 params: &Option<CK_RSA_PKCS_PSS_PARAMS>, 107 ) -> Result<Vec<u8>, Error> { 108 match self { 109 Object::Cert(_) => Err(error_here!(ErrorType::InvalidArgument)), 110 Object::Key(key) => key.sign(&data, params), 111 } 112 } 113 } 114 115 /// Helper struct to manage the state of a single slot, backed by a given `ClientCertsBackend`. 116 struct Slot<B: ClientCertsBackend> { 117 /// A map of object handles to the underlying objects. 118 objects: BTreeMap<CK_OBJECT_HANDLE, Object<B>>, 119 /// A set of certificate identifiers (not the same as handles). 120 cert_ids: BTreeSet<Vec<u8>>, 121 /// A set of key identifiers (not the same as handles). For each id in this set, there should be 122 /// a corresponding identical id in the `cert_ids` set. 123 key_ids: BTreeSet<Vec<u8>>, 124 /// The next object handle to hand out. 125 next_handle: CK_OBJECT_HANDLE, 126 /// The backend that provides objects, signing, etc. 127 backend: B, 128 } 129 130 impl<B: ClientCertsBackend> Slot<B> { 131 fn new(backend: B) -> Slot<B> { 132 Slot { 133 objects: BTreeMap::new(), 134 cert_ids: BTreeSet::new(), 135 key_ids: BTreeSet::new(), 136 next_handle: 1, 137 backend, 138 } 139 } 140 141 fn get_next_handle(&mut self) -> CK_OBJECT_HANDLE { 142 let next_handle = self.next_handle; 143 self.next_handle += 1; 144 next_handle 145 } 146 147 /// When a new search session is opened, this searches for certificates and keys to expose. We 148 /// de-duplicate previously-found certificates and keys by keeping track of their IDs. 149 fn maybe_find_new_objects(&mut self) -> Result<(), Error> { 150 let (certs, keys) = self.backend.find_objects()?; 151 for cert in certs { 152 let object = Object::Cert(cert); 153 if self.cert_ids.contains(object.id()?) { 154 continue; 155 } 156 self.cert_ids.insert(object.id()?.to_vec()); 157 let handle = self.get_next_handle(); 158 self.objects.insert(handle, object); 159 } 160 for key in keys { 161 let object = Object::Key(key); 162 if self.key_ids.contains(object.id()?) { 163 continue; 164 } 165 self.key_ids.insert(object.id()?.to_vec()); 166 let handle = self.get_next_handle(); 167 self.objects.insert(handle, object); 168 } 169 Ok(()) 170 } 171 } 172 173 /// The `Manager` keeps track of the state of this module with respect to the PKCS #11 174 /// specification. This includes what sessions are open, which search and sign operations are 175 /// ongoing, and what objects are known and by what handle. 176 pub struct Manager<B: ClientCertsBackend, S: IsSearchingForClientCerts> { 177 /// A map of open session handle to slot ID. Sessions can be created (opened) on a particular 178 /// slot and later closed. 179 sessions: BTreeMap<CK_SESSION_HANDLE, CK_SLOT_ID>, 180 /// A map of searches to PKCS #11 object handles that match those searches. 181 searches: BTreeMap<CK_SESSION_HANDLE, Vec<CK_OBJECT_HANDLE>>, 182 /// A map of sign operations to a pair of the object handle and optionally some params being 183 /// used by each one. 184 signs: BTreeMap<CK_SESSION_HANDLE, (CK_OBJECT_HANDLE, Option<CK_RSA_PKCS_PSS_PARAMS>)>, 185 /// The next session handle to hand out. 186 next_session: CK_SESSION_HANDLE, 187 /// The list of slots managed by this Manager. The slot at index n has slot ID n + 1. 188 slots: Vec<Slot<B>>, 189 phantom: PhantomData<S>, 190 } 191 192 impl<B: ClientCertsBackend, S: IsSearchingForClientCerts> Manager<B, S> { 193 pub fn new(slots: Vec<B>) -> Manager<B, S> { 194 Manager { 195 sessions: BTreeMap::new(), 196 searches: BTreeMap::new(), 197 signs: BTreeMap::new(), 198 next_session: 1, 199 slots: slots.into_iter().map(Slot::new).collect(), 200 phantom: PhantomData, 201 } 202 } 203 204 /// Get a list of slot IDs. If `token_present` is `true`, returns only the IDs of present 205 /// slots. Otherwise, returns all slot IDs. 206 pub fn get_slot_ids(&self, token_present: bool) -> Vec<CK_SLOT_ID> { 207 let mut slot_ids = Vec::with_capacity(self.slots.len()); 208 for (index, slot) in self.slots.iter().enumerate() { 209 if slot.backend.get_slot_info().flags & CKF_TOKEN_PRESENT == CKF_TOKEN_PRESENT 210 || !token_present 211 { 212 slot_ids.push((index + 1).try_into().unwrap()); 213 } 214 } 215 slot_ids 216 } 217 218 pub fn get_slot_info(&self, slot_id: CK_SLOT_ID) -> Result<CK_SLOT_INFO, Error> { 219 let slot = self.slot_id_to_slot(slot_id)?; 220 Ok(slot.backend.get_slot_info()) 221 } 222 223 pub fn get_token_info(&self, slot_id: CK_SLOT_ID) -> Result<CK_TOKEN_INFO, Error> { 224 let slot = self.slot_id_to_slot(slot_id)?; 225 Ok(slot.backend.get_token_info()) 226 } 227 228 pub fn get_mechanism_list(&self, slot_id: CK_SLOT_ID) -> Result<Vec<CK_MECHANISM_TYPE>, Error> { 229 let slot = self.slot_id_to_slot(slot_id)?; 230 Ok(slot.backend.get_mechanism_list()) 231 } 232 233 pub fn open_session(&mut self, slot_id: CK_SLOT_ID) -> Result<CK_SESSION_HANDLE, Error> { 234 let next_session = self.next_session; 235 self.next_session += 1; 236 self.sessions.insert(next_session, slot_id); 237 Ok(next_session) 238 } 239 240 pub fn close_session(&mut self, session: CK_SESSION_HANDLE) -> Result<(), Error> { 241 self.sessions 242 .remove(&session) 243 .map(|_| ()) 244 .ok_or(error_here!(ErrorType::InvalidInput)) 245 } 246 247 pub fn close_all_sessions(&mut self, slot_id: CK_SLOT_ID) -> Result<(), Error> { 248 self.sessions 249 .retain(|_, existing_slot_id| *existing_slot_id != slot_id); 250 Ok(()) 251 } 252 253 pub fn login(&mut self, session: CK_SESSION_HANDLE) -> Result<(), Error> { 254 let Some(slot_id) = self.sessions.get(&session) else { 255 return Err(error_here!(ErrorType::InvalidArgument)); 256 }; 257 let slot = self.slot_id_to_slot_mut(*slot_id)?; 258 slot.backend.login(); 259 Ok(()) 260 } 261 262 pub fn logout(&mut self, session: CK_SESSION_HANDLE) -> Result<(), Error> { 263 let Some(slot_id) = self.sessions.get(&session) else { 264 return Err(error_here!(ErrorType::InvalidArgument)); 265 }; 266 let slot = self.slot_id_to_slot_mut(*slot_id)?; 267 slot.backend.logout(); 268 Ok(()) 269 } 270 271 pub fn get_session_info(&self, session: CK_SESSION_HANDLE) -> Result<CK_SESSION_INFO, Error> { 272 let Some(slot_id) = self.sessions.get(&session) else { 273 return Err(error_here!(ErrorType::InvalidArgument)); 274 }; 275 let slot = self.slot_id_to_slot(*slot_id)?; 276 Ok(CK_SESSION_INFO { 277 slotID: *slot_id, 278 state: if slot.backend.is_logged_in() { 279 CKS_RO_USER_FUNCTIONS 280 } else { 281 CKS_RO_PUBLIC_SESSION 282 }, 283 flags: CKF_SERIAL_SESSION, 284 ulDeviceError: 0, 285 }) 286 } 287 288 fn slot_id_to_slot(&self, slot_id: CK_SLOT_ID) -> Result<&Slot<B>, Error> { 289 let slot_id: usize = slot_id 290 .try_into() 291 .map_err(|_| error_here!(ErrorType::InvalidInput))?; 292 if slot_id < 1 { 293 return Err(error_here!(ErrorType::InvalidInput)); 294 } 295 self.slots 296 .get(slot_id - 1) 297 .ok_or(error_here!(ErrorType::InvalidInput)) 298 } 299 300 fn slot_id_to_slot_mut(&mut self, slot_id: CK_SLOT_ID) -> Result<&mut Slot<B>, Error> { 301 let slot_id: usize = slot_id 302 .try_into() 303 .map_err(|_| error_here!(ErrorType::InvalidInput))?; 304 if slot_id < 1 { 305 return Err(error_here!(ErrorType::InvalidInput)); 306 } 307 self.slots 308 .get_mut(slot_id - 1) 309 .ok_or(error_here!(ErrorType::InvalidInput)) 310 } 311 312 /// PKCS #11 specifies that search operations happen in three phases: setup, get any matches 313 /// (this part may be repeated if the caller uses a small buffer), and end. This implementation 314 /// does all of the work up front and gathers all matching objects during setup and retains them 315 /// until they are retrieved and consumed via `search`. 316 pub fn start_search( 317 &mut self, 318 session: CK_SESSION_HANDLE, 319 attrs: Vec<(CK_ATTRIBUTE_TYPE, Vec<u8>)>, 320 ) -> Result<(), Error> { 321 let Some(slot_id) = self.sessions.get(&session) else { 322 return Err(error_here!(ErrorType::InvalidArgument)); 323 }; 324 let slot = self.slot_id_to_slot_mut(*slot_id)?; 325 // If the search is for an attribute we don't support, no objects will match. This check 326 // saves us having to look through all of our objects. 327 for (attr, _) in &attrs { 328 if !SUPPORTED_ATTRIBUTES.contains(attr) { 329 self.searches.insert(session, Vec::new()); 330 return Ok(()); 331 } 332 } 333 // Only search for new objects when gecko has indicated that it is looking for client 334 // authentication certificates (or all certificates). 335 // Since these searches are relatively rare, this minimizes the impact of doing these 336 // re-scans. 337 if S::is_searching_for_client_certs() { 338 slot.maybe_find_new_objects()?; 339 } 340 let mut handles = Vec::new(); 341 for (handle, object) in &slot.objects { 342 if object.matches(&attrs) { 343 handles.push(*handle); 344 } 345 } 346 self.searches.insert(session, handles); 347 Ok(()) 348 } 349 350 /// Given a session and a maximum number of object handles to return, attempts to retrieve up to 351 /// that many objects from the corresponding search. Updates the search so those objects are not 352 /// returned repeatedly. `max_objects` must be non-zero. 353 pub fn search( 354 &mut self, 355 session: CK_SESSION_HANDLE, 356 max_objects: usize, 357 ) -> Result<Vec<CK_OBJECT_HANDLE>, Error> { 358 if max_objects == 0 { 359 return Err(error_here!(ErrorType::InvalidArgument)); 360 } 361 match self.searches.get_mut(&session) { 362 Some(search) => { 363 let split_at = if max_objects >= search.len() { 364 0 365 } else { 366 search.len() - max_objects 367 }; 368 let to_return = search.split_off(split_at); 369 if to_return.len() > max_objects { 370 return Err(error_here!(ErrorType::LibraryFailure)); 371 } 372 Ok(to_return) 373 } 374 None => Err(error_here!(ErrorType::InvalidArgument)), 375 } 376 } 377 378 pub fn clear_search(&mut self, session: CK_SESSION_HANDLE) -> Result<(), Error> { 379 self.searches.remove(&session); 380 Ok(()) 381 } 382 383 pub fn get_attributes( 384 &self, 385 session: CK_SESSION_HANDLE, 386 object_handle: CK_OBJECT_HANDLE, 387 attr_types: Vec<CK_ATTRIBUTE_TYPE>, 388 ) -> Result<Vec<Option<Vec<u8>>>, Error> { 389 let Some(slot_id) = self.sessions.get(&session) else { 390 return Err(error_here!(ErrorType::InvalidArgument)); 391 }; 392 let slot = self.slot_id_to_slot(*slot_id)?; 393 let object = match slot.objects.get(&object_handle) { 394 Some(object) => object, 395 None => return Err(error_here!(ErrorType::InvalidArgument)), 396 }; 397 let mut results = Vec::with_capacity(attr_types.len()); 398 for attr_type in attr_types { 399 let result = object 400 .get_attribute(attr_type) 401 .map(|value| value.to_owned()); 402 results.push(result); 403 } 404 Ok(results) 405 } 406 407 /// The way NSS uses PKCS #11 to sign data happens in two phases: setup and sign. This 408 /// implementation makes a note of which key is to be used (if it exists) during setup. When the 409 /// caller finishes with the sign operation, this implementation retrieves the key handle and 410 /// performs the signature. 411 pub fn start_sign( 412 &mut self, 413 session: CK_SESSION_HANDLE, 414 key_handle: CK_OBJECT_HANDLE, 415 params: Option<CK_RSA_PKCS_PSS_PARAMS>, 416 ) -> Result<(), Error> { 417 if self.signs.contains_key(&session) { 418 return Err(error_here!(ErrorType::InvalidArgument)); 419 } 420 self.signs.insert(session, (key_handle, params)); 421 Ok(()) 422 } 423 424 pub fn get_signature_length( 425 &mut self, 426 session: CK_SESSION_HANDLE, 427 data: Vec<u8>, 428 ) -> Result<usize, Error> { 429 // Take ownership of the key handle and params of the sign (this has the added benefit of 430 // removing this data in case of an error). 431 let (key_handle, params) = match self.signs.remove(&session) { 432 Some((key_handle, params)) => (key_handle, params), 433 None => return Err(error_here!(ErrorType::InvalidArgument)), 434 }; 435 let Some(slot_id) = self.sessions.get(&session) else { 436 return Err(error_here!(ErrorType::InvalidArgument)); 437 }; 438 let slot = self.slot_id_to_slot_mut(*slot_id)?; 439 let key = match slot.objects.get_mut(&key_handle) { 440 Some(key) => key, 441 None => return Err(error_here!(ErrorType::InvalidArgument)), 442 }; 443 let signature_length = key.get_signature_length(data, ¶ms)?; 444 // Re-add the key handle and params if getting the signature length succeeded. 445 self.signs.insert(session, (key_handle, params)); 446 Ok(signature_length) 447 } 448 449 pub fn sign(&mut self, session: CK_SESSION_HANDLE, data: Vec<u8>) -> Result<Vec<u8>, Error> { 450 // Performing the signature (via C_Sign, which is the only way we support) finishes the sign 451 // operation, so it needs to be removed here. 452 let (key_handle, params) = match self.signs.remove(&session) { 453 Some((key_handle, params)) => (key_handle, params), 454 None => return Err(error_here!(ErrorType::InvalidArgument)), 455 }; 456 let Some(slot_id) = self.sessions.get(&session) else { 457 return Err(error_here!(ErrorType::InvalidArgument)); 458 }; 459 let slot = self.slot_id_to_slot_mut(*slot_id)?; 460 let key = match slot.objects.get_mut(&key_handle) { 461 Some(key) => key, 462 None => return Err(error_here!(ErrorType::InvalidArgument)), 463 }; 464 key.sign(data, ¶ms) 465 } 466 }