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/. */
6 use pkcs11_bindings::*;
7 use std::collections::{BTreeMap, BTreeSet};
8 use std::sync::mpsc::{channel, Receiver, Sender};
10 use std::thread::JoinHandle;
11 use std::time::{Duration, Instant};
13 use crate::error::{Error, ErrorType};
14 use crate::error_here;
17 /// Helper enum to differentiate between sessions on the modern slot and sessions on the legacy
18 /// slot. The former is for EC keys and RSA keys that can be used with RSA-PSS whereas the latter is
19 /// for RSA keys that cannot be used with RSA-PSS.
20 #[derive(Clone, Copy, PartialEq)]
26 pub trait CryptokiObject {
27 fn matches(&self, slot_type: SlotType, attrs: &[(CK_ATTRIBUTE_TYPE, Vec<u8>)]) -> bool;
28 fn get_attribute(&self, attribute: CK_ATTRIBUTE_TYPE) -> Option<&[u8]>;
32 fn get_signature_length(
35 params: &Option<CK_RSA_PKCS_PSS_PARAMS>,
36 ) -> Result<usize, Error>;
40 params: &Option<CK_RSA_PKCS_PSS_PARAMS>,
41 ) -> Result<Vec<u8>, Error>;
44 pub trait ClientCertsBackend {
45 type Cert: CryptokiObject;
46 type Key: CryptokiObject + Sign;
48 #[allow(clippy::type_complexity)]
49 fn find_objects(&self) -> Result<(Vec<Self::Cert>, Vec<Self::Key>), Error>;
52 /// Helper type for sending `ManagerArguments` to the real `Manager`.
53 type ManagerArgumentsSender = Sender<ManagerArguments>;
54 /// Helper type for receiving `ManagerReturnValue`s from the real `Manager`.
55 type ManagerReturnValueReceiver = Receiver<ManagerReturnValue>;
57 /// Helper enum that encapsulates arguments to send from the `ManagerProxy` to the real `Manager`.
58 /// `ManagerArguments::Stop` is a special variant that stops the background thread and drops the
60 enum ManagerArguments {
61 OpenSession(SlotType),
62 CloseSession(CK_SESSION_HANDLE),
63 CloseAllSessions(SlotType),
64 StartSearch(CK_SESSION_HANDLE, Vec<(CK_ATTRIBUTE_TYPE, Vec<u8>)>),
65 Search(CK_SESSION_HANDLE, usize),
66 ClearSearch(CK_SESSION_HANDLE),
67 GetAttributes(CK_OBJECT_HANDLE, Vec<CK_ATTRIBUTE_TYPE>),
71 Option<CK_RSA_PKCS_PSS_PARAMS>,
73 GetSignatureLength(CK_SESSION_HANDLE, Vec<u8>),
74 Sign(CK_SESSION_HANDLE, Vec<u8>),
78 /// Helper enum that encapsulates return values from the real `Manager` that are sent back to the
79 /// `ManagerProxy`. `ManagerReturnValue::Stop` is a special variant that indicates that the
80 /// `Manager` will stop.
81 enum ManagerReturnValue {
82 OpenSession(Result<CK_SESSION_HANDLE, Error>),
83 CloseSession(Result<(), Error>),
84 CloseAllSessions(Result<(), Error>),
85 StartSearch(Result<(), Error>),
86 Search(Result<Vec<CK_OBJECT_HANDLE>, Error>),
87 ClearSearch(Result<(), Error>),
88 GetAttributes(Result<Vec<Option<Vec<u8>>>, Error>),
89 StartSign(Result<(), Error>),
90 GetSignatureLength(Result<usize, Error>),
91 Sign(Result<Vec<u8>, Error>),
92 Stop(Result<(), Error>),
95 /// Helper macro to implement the body of each public `ManagerProxy` function. Takes a
96 /// `ManagerProxy` instance (should always be `self`), a `ManagerArguments` representing the
97 /// `Manager` function to call and the arguments to use, and the qualified type of the expected
98 /// `ManagerReturnValue` that will be received from the `Manager` when it is done.
99 macro_rules! manager_proxy_fn_impl {
100 ($manager:ident, $argument_enum:expr, $return_type:path) => {
101 match $manager.proxy_call($argument_enum) {
102 Ok($return_type(result)) => result,
103 Ok(_) => Err(error_here!(ErrorType::LibraryFailure)),
109 /// `ManagerProxy` synchronously proxies calls from any thread to the `Manager` that runs on a
110 /// single thread. This is necessary because the underlying OS APIs in use are not guaranteed to be
111 /// thread-safe (e.g. they may use thread-local storage). Using it should be identical to using the
113 pub struct ManagerProxy {
114 sender: ManagerArgumentsSender,
115 receiver: ManagerReturnValueReceiver,
116 thread_handle: Option<JoinHandle<()>>,
120 pub fn new<B: ClientCertsBackend + Send + 'static>(backend: B) -> Result<ManagerProxy, Error> {
121 let (proxy_sender, manager_receiver) = channel();
122 let (manager_sender, proxy_receiver) = channel();
123 let thread_handle = thread::Builder::new()
124 .name("osclientcert".into())
126 let mut real_manager = Manager::new(backend);
127 while let Ok(arguments) = manager_receiver.recv() {
128 let results = match arguments {
129 ManagerArguments::OpenSession(slot_type) => {
130 ManagerReturnValue::OpenSession(real_manager.open_session(slot_type))
132 ManagerArguments::CloseSession(session_handle) => {
133 ManagerReturnValue::CloseSession(
134 real_manager.close_session(session_handle),
137 ManagerArguments::CloseAllSessions(slot_type) => {
138 ManagerReturnValue::CloseAllSessions(
139 real_manager.close_all_sessions(slot_type),
142 ManagerArguments::StartSearch(session, attrs) => {
143 ManagerReturnValue::StartSearch(
144 real_manager.start_search(session, attrs),
147 ManagerArguments::Search(session, max_objects) => {
148 ManagerReturnValue::Search(real_manager.search(session, max_objects))
150 ManagerArguments::ClearSearch(session) => {
151 ManagerReturnValue::ClearSearch(real_manager.clear_search(session))
153 ManagerArguments::GetAttributes(object_handle, attr_types) => {
154 ManagerReturnValue::GetAttributes(
155 real_manager.get_attributes(object_handle, attr_types),
158 ManagerArguments::StartSign(session, key_handle, params) => {
159 ManagerReturnValue::StartSign(
160 real_manager.start_sign(session, key_handle, params),
163 ManagerArguments::GetSignatureLength(session, data) => {
164 ManagerReturnValue::GetSignatureLength(
165 real_manager.get_signature_length(session, data),
168 ManagerArguments::Sign(session, data) => {
169 ManagerReturnValue::Sign(real_manager.sign(session, data))
171 ManagerArguments::Stop => ManagerReturnValue::Stop(Ok(())),
173 let stop_after_send = matches!(&results, &ManagerReturnValue::Stop(_));
174 match manager_sender.send(results) {
185 match thread_handle {
186 Ok(thread_handle) => Ok(ManagerProxy {
187 sender: proxy_sender,
188 receiver: proxy_receiver,
189 thread_handle: Some(thread_handle),
191 Err(_) => Err(error_here!(ErrorType::LibraryFailure)),
195 fn proxy_call(&self, args: ManagerArguments) -> Result<ManagerReturnValue, Error> {
196 match self.sender.send(args) {
199 return Err(error_here!(ErrorType::LibraryFailure));
202 let result = match self.receiver.recv() {
203 Ok(result) => result,
205 return Err(error_here!(ErrorType::LibraryFailure));
211 pub fn open_session(&mut self, slot_type: SlotType) -> Result<CK_SESSION_HANDLE, Error> {
212 manager_proxy_fn_impl!(
214 ManagerArguments::OpenSession(slot_type),
215 ManagerReturnValue::OpenSession
219 pub fn close_session(&mut self, session: CK_SESSION_HANDLE) -> Result<(), Error> {
220 manager_proxy_fn_impl!(
222 ManagerArguments::CloseSession(session),
223 ManagerReturnValue::CloseSession
227 pub fn close_all_sessions(&mut self, slot_type: SlotType) -> Result<(), Error> {
228 manager_proxy_fn_impl!(
230 ManagerArguments::CloseAllSessions(slot_type),
231 ManagerReturnValue::CloseAllSessions
237 session: CK_SESSION_HANDLE,
238 attrs: Vec<(CK_ATTRIBUTE_TYPE, Vec<u8>)>,
239 ) -> Result<(), Error> {
240 manager_proxy_fn_impl!(
242 ManagerArguments::StartSearch(session, attrs),
243 ManagerReturnValue::StartSearch
249 session: CK_SESSION_HANDLE,
251 ) -> Result<Vec<CK_OBJECT_HANDLE>, Error> {
252 manager_proxy_fn_impl!(
254 ManagerArguments::Search(session, max_objects),
255 ManagerReturnValue::Search
259 pub fn clear_search(&mut self, session: CK_SESSION_HANDLE) -> Result<(), Error> {
260 manager_proxy_fn_impl!(
262 ManagerArguments::ClearSearch(session),
263 ManagerReturnValue::ClearSearch
267 pub fn get_attributes(
269 object_handle: CK_OBJECT_HANDLE,
270 attr_types: Vec<CK_ATTRIBUTE_TYPE>,
271 ) -> Result<Vec<Option<Vec<u8>>>, Error> {
272 manager_proxy_fn_impl!(
274 ManagerArguments::GetAttributes(object_handle, attr_types,),
275 ManagerReturnValue::GetAttributes
281 session: CK_SESSION_HANDLE,
282 key_handle: CK_OBJECT_HANDLE,
283 params: Option<CK_RSA_PKCS_PSS_PARAMS>,
284 ) -> Result<(), Error> {
285 manager_proxy_fn_impl!(
287 ManagerArguments::StartSign(session, key_handle, params),
288 ManagerReturnValue::StartSign
292 pub fn get_signature_length(
294 session: CK_SESSION_HANDLE,
296 ) -> Result<usize, Error> {
297 manager_proxy_fn_impl!(
299 ManagerArguments::GetSignatureLength(session, data),
300 ManagerReturnValue::GetSignatureLength
304 pub fn sign(&mut self, session: CK_SESSION_HANDLE, data: Vec<u8>) -> Result<Vec<u8>, Error> {
305 manager_proxy_fn_impl!(
307 ManagerArguments::Sign(session, data),
308 ManagerReturnValue::Sign
312 pub fn stop(&mut self) -> Result<(), Error> {
313 manager_proxy_fn_impl!(self, ManagerArguments::Stop, ManagerReturnValue::Stop)?;
314 let thread_handle = match self.thread_handle.take() {
315 Some(thread_handle) => thread_handle,
316 None => return Err(error_here!(ErrorType::LibraryFailure)),
320 .map_err(|_| error_here!(ErrorType::LibraryFailure))
324 // Determines if the attributes of a given search correspond to NSS looking for all certificates or
325 // private keys. Returns true if so, and false otherwise.
326 // These searches are of the form:
327 // { { type: CKA_TOKEN, value: [1] },
328 // { type: CKA_CLASS, value: [CKO_CERTIFICATE or CKO_PRIVATE_KEY, as serialized bytes] } }
329 // (although not necessarily in that order - see nssToken_TraverseCertificates and
330 // nssToken_FindPrivateKeys)
331 fn search_is_for_all_certificates_or_keys(
332 attrs: &[(CK_ATTRIBUTE_TYPE, Vec<u8>)],
333 ) -> Result<bool, Error> {
334 if attrs.len() != 2 {
337 let token_bytes = vec![1_u8];
338 let mut found_token = false;
339 let cko_certificate_bytes = serialize_uint(CKO_CERTIFICATE)?;
340 let cko_private_key_bytes = serialize_uint(CKO_PRIVATE_KEY)?;
341 let mut found_certificate_or_private_key = false;
342 for (attr_type, attr_value) in attrs.iter() {
343 if attr_type == &CKA_TOKEN && attr_value == &token_bytes {
346 if attr_type == &CKA_CLASS
347 && (attr_value == &cko_certificate_bytes || attr_value == &cko_private_key_bytes)
349 found_certificate_or_private_key = true;
352 Ok(found_token && found_certificate_or_private_key)
355 const SUPPORTED_ATTRIBUTES: &[CK_ATTRIBUTE_TYPE] = &[
370 enum Object<B: ClientCertsBackend> {
375 impl<B: ClientCertsBackend> Object<B> {
376 fn matches(&self, slot_type: SlotType, attrs: &[(CK_ATTRIBUTE_TYPE, Vec<u8>)]) -> bool {
378 Object::Cert(cert) => cert.matches(slot_type, attrs),
379 Object::Key(key) => key.matches(slot_type, attrs),
383 fn get_attribute(&self, attribute: CK_ATTRIBUTE_TYPE) -> Option<&[u8]> {
385 Object::Cert(cert) => cert.get_attribute(attribute),
386 Object::Key(key) => key.get_attribute(attribute),
390 fn id(&self) -> Result<&[u8], Error> {
391 self.get_attribute(CKA_ID)
392 .ok_or_else(|| error_here!(ErrorType::LibraryFailure))
395 fn get_signature_length(
398 params: &Option<CK_RSA_PKCS_PSS_PARAMS>,
399 ) -> Result<usize, Error> {
401 Object::Cert(_) => Err(error_here!(ErrorType::InvalidArgument)),
402 Object::Key(key) => key.get_signature_length(&data, params),
409 params: &Option<CK_RSA_PKCS_PSS_PARAMS>,
410 ) -> Result<Vec<u8>, Error> {
412 Object::Cert(_) => Err(error_here!(ErrorType::InvalidArgument)),
413 Object::Key(key) => key.sign(&data, params),
418 /// The `Manager` keeps track of the state of this module with respect to the PKCS #11
419 /// specification. This includes what sessions are open, which search and sign operations are
420 /// ongoing, and what objects are known and by what handle.
421 pub struct Manager<B: ClientCertsBackend> {
422 /// A map of session to session type (modern or legacy). Sessions can be created (opened) and
424 sessions: BTreeMap<CK_SESSION_HANDLE, SlotType>,
425 /// A map of searches to PKCS #11 object handles that match those searches.
426 searches: BTreeMap<CK_SESSION_HANDLE, Vec<CK_OBJECT_HANDLE>>,
427 /// A map of sign operations to a pair of the object handle and optionally some params being
428 /// used by each one.
429 signs: BTreeMap<CK_SESSION_HANDLE, (CK_OBJECT_HANDLE, Option<CK_RSA_PKCS_PSS_PARAMS>)>,
430 /// A map of object handles to the underlying objects.
431 objects: BTreeMap<CK_OBJECT_HANDLE, Object<B>>,
432 /// A set of certificate identifiers (not the same as handles).
433 cert_ids: BTreeSet<Vec<u8>>,
434 /// A set of key identifiers (not the same as handles). For each id in this set, there should be
435 /// a corresponding identical id in the `cert_ids` set.
436 key_ids: BTreeSet<Vec<u8>>,
437 /// The next session handle to hand out.
438 next_session: CK_SESSION_HANDLE,
439 /// The next object handle to hand out.
440 next_handle: CK_OBJECT_HANDLE,
441 /// The last time the implementation looked for new objects in the backend.
442 /// The implementation does this search no more than once every 3 seconds.
443 last_scan_time: Option<Instant>,
447 impl<B: ClientCertsBackend> Manager<B> {
448 pub fn new(backend: B) -> Manager<B> {
450 sessions: BTreeMap::new(),
451 searches: BTreeMap::new(),
452 signs: BTreeMap::new(),
453 objects: BTreeMap::new(),
454 cert_ids: BTreeSet::new(),
455 key_ids: BTreeSet::new(),
458 last_scan_time: None,
463 /// When a new search session is opened (provided at least 3 seconds have elapsed since the
464 /// last session was opened), this searches for certificates and keys to expose. We
465 /// de-duplicate previously-found certificates and keys by keeping track of their IDs.
466 fn maybe_find_new_objects(&mut self) -> Result<(), Error> {
467 let now = Instant::now();
468 match self.last_scan_time {
469 Some(last_scan_time) => {
470 if now.duration_since(last_scan_time) < Duration::new(3, 0) {
476 self.last_scan_time = Some(now);
477 let (certs, keys) = self.backend.find_objects()?;
479 let object = Object::Cert(cert);
480 if self.cert_ids.contains(object.id()?) {
483 self.cert_ids.insert(object.id()?.to_vec());
484 let handle = self.get_next_handle();
485 self.objects.insert(handle, object);
488 let object = Object::Key(key);
489 if self.key_ids.contains(object.id()?) {
492 self.key_ids.insert(object.id()?.to_vec());
493 let handle = self.get_next_handle();
494 self.objects.insert(handle, object);
499 pub fn open_session(&mut self, slot_type: SlotType) -> Result<CK_SESSION_HANDLE, Error> {
500 let next_session = self.next_session;
501 self.next_session += 1;
502 self.sessions.insert(next_session, slot_type);
506 pub fn close_session(&mut self, session: CK_SESSION_HANDLE) -> Result<(), Error> {
509 .ok_or_else(|| error_here!(ErrorType::InvalidInput))
513 pub fn close_all_sessions(&mut self, slot_type: SlotType) -> Result<(), Error> {
514 let mut to_remove = Vec::new();
515 for (session, open_slot_type) in self.sessions.iter() {
516 if slot_type == *open_slot_type {
517 to_remove.push(*session);
520 for session in to_remove {
521 if self.sessions.remove(&session).is_none() {
522 return Err(error_here!(ErrorType::LibraryFailure));
528 fn get_next_handle(&mut self) -> CK_OBJECT_HANDLE {
529 let next_handle = self.next_handle;
530 self.next_handle += 1;
534 /// PKCS #11 specifies that search operations happen in three phases: setup, get any matches
535 /// (this part may be repeated if the caller uses a small buffer), and end. This implementation
536 /// does all of the work up front and gathers all matching objects during setup and retains them
537 /// until they are retrieved and consumed via `search`.
540 session: CK_SESSION_HANDLE,
541 attrs: Vec<(CK_ATTRIBUTE_TYPE, Vec<u8>)>,
542 ) -> Result<(), Error> {
543 let slot_type = match self.sessions.get(&session) {
544 Some(slot_type) => *slot_type,
545 None => return Err(error_here!(ErrorType::InvalidArgument)),
547 // If the search is for an attribute we don't support, no objects will match. This check
548 // saves us having to look through all of our objects.
549 for (attr, _) in &attrs {
550 if !SUPPORTED_ATTRIBUTES.contains(attr) {
551 self.searches.insert(session, Vec::new());
555 // When NSS wants to find all certificates or all private keys, it will perform a search
556 // with a particular set of attributes. This implementation uses these searches as an
557 // indication for the backend to re-scan for new objects from tokens that may have been
558 // inserted or certificates that may have been imported into the OS. Since these searches
559 // are relatively rare, this minimizes the impact of doing these re-scans.
560 if search_is_for_all_certificates_or_keys(&attrs)? {
561 self.maybe_find_new_objects()?;
563 let mut handles = Vec::new();
564 for (handle, object) in &self.objects {
565 if object.matches(slot_type, &attrs) {
566 handles.push(*handle);
569 self.searches.insert(session, handles);
573 /// Given a session and a maximum number of object handles to return, attempts to retrieve up to
574 /// that many objects from the corresponding search. Updates the search so those objects are not
575 /// returned repeatedly. `max_objects` must be non-zero.
578 session: CK_SESSION_HANDLE,
580 ) -> Result<Vec<CK_OBJECT_HANDLE>, Error> {
581 if max_objects == 0 {
582 return Err(error_here!(ErrorType::InvalidArgument));
584 match self.searches.get_mut(&session) {
586 let split_at = if max_objects >= search.len() {
589 search.len() - max_objects
591 let to_return = search.split_off(split_at);
592 if to_return.len() > max_objects {
593 return Err(error_here!(ErrorType::LibraryFailure));
597 None => Err(error_here!(ErrorType::InvalidArgument)),
601 pub fn clear_search(&mut self, session: CK_SESSION_HANDLE) -> Result<(), Error> {
602 self.searches.remove(&session);
606 pub fn get_attributes(
608 object_handle: CK_OBJECT_HANDLE,
609 attr_types: Vec<CK_ATTRIBUTE_TYPE>,
610 ) -> Result<Vec<Option<Vec<u8>>>, Error> {
611 let object = match self.objects.get(&object_handle) {
612 Some(object) => object,
613 None => return Err(error_here!(ErrorType::InvalidArgument)),
615 let mut results = Vec::with_capacity(attr_types.len());
616 for attr_type in attr_types {
618 .get_attribute(attr_type)
619 .map(|value| value.to_owned());
620 results.push(result);
625 /// The way NSS uses PKCS #11 to sign data happens in two phases: setup and sign. This
626 /// implementation makes a note of which key is to be used (if it exists) during setup. When the
627 /// caller finishes with the sign operation, this implementation retrieves the key handle and
628 /// performs the signature.
631 session: CK_SESSION_HANDLE,
632 key_handle: CK_OBJECT_HANDLE,
633 params: Option<CK_RSA_PKCS_PSS_PARAMS>,
634 ) -> Result<(), Error> {
635 if self.signs.contains_key(&session) {
636 return Err(error_here!(ErrorType::InvalidArgument));
638 self.signs.insert(session, (key_handle, params));
642 pub fn get_signature_length(
644 session: CK_SESSION_HANDLE,
646 ) -> Result<usize, Error> {
647 let (key_handle, params) = match self.signs.get(&session) {
648 Some((key_handle, params)) => (key_handle, params),
649 None => return Err(error_here!(ErrorType::InvalidArgument)),
651 let key = match self.objects.get_mut(key_handle) {
653 None => return Err(error_here!(ErrorType::InvalidArgument)),
655 key.get_signature_length(data, params)
658 pub fn sign(&mut self, session: CK_SESSION_HANDLE, data: Vec<u8>) -> Result<Vec<u8>, Error> {
659 // Performing the signature (via C_Sign, which is the only way we support) finishes the sign
660 // operation, so it needs to be removed here.
661 let (key_handle, params) = match self.signs.remove(&session) {
662 Some((key_handle, params)) => (key_handle, params),
663 None => return Err(error_here!(ErrorType::InvalidArgument)),
665 let key = match self.objects.get_mut(&key_handle) {
667 None => return Err(error_here!(ErrorType::InvalidArgument)),
669 key.sign(data, ¶ms)