/* -*- Mode: rust; rust-indent-offset: 4 -*- */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use pkcs11_bindings::*; use std::collections::{BTreeMap, BTreeSet}; use std::sync::mpsc::{channel, Receiver, Sender}; use std::thread; use std::thread::JoinHandle; use std::time::{Duration, Instant}; use crate::error::{Error, ErrorType}; use crate::error_here; use crate::util::*; /// Helper enum to differentiate between sessions on the modern slot and sessions on the legacy /// slot. The former is for EC keys and RSA keys that can be used with RSA-PSS whereas the latter is /// for RSA keys that cannot be used with RSA-PSS. #[derive(Clone, Copy, PartialEq)] pub enum SlotType { Modern, Legacy, } pub trait CryptokiObject { fn matches(&self, slot_type: SlotType, attrs: &[(CK_ATTRIBUTE_TYPE, Vec)]) -> bool; fn get_attribute(&self, attribute: CK_ATTRIBUTE_TYPE) -> Option<&[u8]>; } pub trait Sign { fn get_signature_length( &mut self, data: &[u8], params: &Option, ) -> Result; fn sign( &mut self, data: &[u8], params: &Option, ) -> Result, Error>; } pub trait ClientCertsBackend { type Cert: CryptokiObject; type Key: CryptokiObject + Sign; #[allow(clippy::type_complexity)] fn find_objects(&self) -> Result<(Vec, Vec), Error>; } /// Helper type for sending `ManagerArguments` to the real `Manager`. type ManagerArgumentsSender = Sender; /// Helper type for receiving `ManagerReturnValue`s from the real `Manager`. type ManagerReturnValueReceiver = Receiver; /// Helper enum that encapsulates arguments to send from the `ManagerProxy` to the real `Manager`. /// `ManagerArguments::Stop` is a special variant that stops the background thread and drops the /// `Manager`. enum ManagerArguments { OpenSession(SlotType), CloseSession(CK_SESSION_HANDLE), CloseAllSessions(SlotType), StartSearch(CK_SESSION_HANDLE, Vec<(CK_ATTRIBUTE_TYPE, Vec)>), Search(CK_SESSION_HANDLE, usize), ClearSearch(CK_SESSION_HANDLE), GetAttributes(CK_OBJECT_HANDLE, Vec), StartSign( CK_SESSION_HANDLE, CK_OBJECT_HANDLE, Option, ), GetSignatureLength(CK_SESSION_HANDLE, Vec), Sign(CK_SESSION_HANDLE, Vec), Stop, } /// Helper enum that encapsulates return values from the real `Manager` that are sent back to the /// `ManagerProxy`. `ManagerReturnValue::Stop` is a special variant that indicates that the /// `Manager` will stop. enum ManagerReturnValue { OpenSession(Result), CloseSession(Result<(), Error>), CloseAllSessions(Result<(), Error>), StartSearch(Result<(), Error>), Search(Result, Error>), ClearSearch(Result<(), Error>), GetAttributes(Result>>, Error>), StartSign(Result<(), Error>), GetSignatureLength(Result), Sign(Result, Error>), Stop(Result<(), Error>), } /// Helper macro to implement the body of each public `ManagerProxy` function. Takes a /// `ManagerProxy` instance (should always be `self`), a `ManagerArguments` representing the /// `Manager` function to call and the arguments to use, and the qualified type of the expected /// `ManagerReturnValue` that will be received from the `Manager` when it is done. macro_rules! manager_proxy_fn_impl { ($manager:ident, $argument_enum:expr, $return_type:path) => { match $manager.proxy_call($argument_enum) { Ok($return_type(result)) => result, Ok(_) => Err(error_here!(ErrorType::LibraryFailure)), Err(e) => Err(e), } }; } /// `ManagerProxy` synchronously proxies calls from any thread to the `Manager` that runs on a /// single thread. This is necessary because the underlying OS APIs in use are not guaranteed to be /// thread-safe (e.g. they may use thread-local storage). Using it should be identical to using the /// real `Manager`. pub struct ManagerProxy { sender: ManagerArgumentsSender, receiver: ManagerReturnValueReceiver, thread_handle: Option>, } impl ManagerProxy { pub fn new(backend: B) -> Result { let (proxy_sender, manager_receiver) = channel(); let (manager_sender, proxy_receiver) = channel(); let thread_handle = thread::Builder::new() .name("osclientcert".into()) .spawn(move || { let mut real_manager = Manager::new(backend); while let Ok(arguments) = manager_receiver.recv() { let results = match arguments { ManagerArguments::OpenSession(slot_type) => { ManagerReturnValue::OpenSession(real_manager.open_session(slot_type)) } ManagerArguments::CloseSession(session_handle) => { ManagerReturnValue::CloseSession( real_manager.close_session(session_handle), ) } ManagerArguments::CloseAllSessions(slot_type) => { ManagerReturnValue::CloseAllSessions( real_manager.close_all_sessions(slot_type), ) } ManagerArguments::StartSearch(session, attrs) => { ManagerReturnValue::StartSearch( real_manager.start_search(session, attrs), ) } ManagerArguments::Search(session, max_objects) => { ManagerReturnValue::Search(real_manager.search(session, max_objects)) } ManagerArguments::ClearSearch(session) => { ManagerReturnValue::ClearSearch(real_manager.clear_search(session)) } ManagerArguments::GetAttributes(object_handle, attr_types) => { ManagerReturnValue::GetAttributes( real_manager.get_attributes(object_handle, attr_types), ) } ManagerArguments::StartSign(session, key_handle, params) => { ManagerReturnValue::StartSign( real_manager.start_sign(session, key_handle, params), ) } ManagerArguments::GetSignatureLength(session, data) => { ManagerReturnValue::GetSignatureLength( real_manager.get_signature_length(session, data), ) } ManagerArguments::Sign(session, data) => { ManagerReturnValue::Sign(real_manager.sign(session, data)) } ManagerArguments::Stop => ManagerReturnValue::Stop(Ok(())), }; let stop_after_send = matches!(&results, &ManagerReturnValue::Stop(_)); match manager_sender.send(results) { Ok(()) => {} Err(_) => { break; } } if stop_after_send { break; } } }); match thread_handle { Ok(thread_handle) => Ok(ManagerProxy { sender: proxy_sender, receiver: proxy_receiver, thread_handle: Some(thread_handle), }), Err(_) => Err(error_here!(ErrorType::LibraryFailure)), } } fn proxy_call(&self, args: ManagerArguments) -> Result { match self.sender.send(args) { Ok(()) => {} Err(_) => { return Err(error_here!(ErrorType::LibraryFailure)); } }; let result = match self.receiver.recv() { Ok(result) => result, Err(_) => { return Err(error_here!(ErrorType::LibraryFailure)); } }; Ok(result) } pub fn open_session(&mut self, slot_type: SlotType) -> Result { manager_proxy_fn_impl!( self, ManagerArguments::OpenSession(slot_type), ManagerReturnValue::OpenSession ) } pub fn close_session(&mut self, session: CK_SESSION_HANDLE) -> Result<(), Error> { manager_proxy_fn_impl!( self, ManagerArguments::CloseSession(session), ManagerReturnValue::CloseSession ) } pub fn close_all_sessions(&mut self, slot_type: SlotType) -> Result<(), Error> { manager_proxy_fn_impl!( self, ManagerArguments::CloseAllSessions(slot_type), ManagerReturnValue::CloseAllSessions ) } pub fn start_search( &mut self, session: CK_SESSION_HANDLE, attrs: Vec<(CK_ATTRIBUTE_TYPE, Vec)>, ) -> Result<(), Error> { manager_proxy_fn_impl!( self, ManagerArguments::StartSearch(session, attrs), ManagerReturnValue::StartSearch ) } pub fn search( &mut self, session: CK_SESSION_HANDLE, max_objects: usize, ) -> Result, Error> { manager_proxy_fn_impl!( self, ManagerArguments::Search(session, max_objects), ManagerReturnValue::Search ) } pub fn clear_search(&mut self, session: CK_SESSION_HANDLE) -> Result<(), Error> { manager_proxy_fn_impl!( self, ManagerArguments::ClearSearch(session), ManagerReturnValue::ClearSearch ) } pub fn get_attributes( &self, object_handle: CK_OBJECT_HANDLE, attr_types: Vec, ) -> Result>>, Error> { manager_proxy_fn_impl!( self, ManagerArguments::GetAttributes(object_handle, attr_types,), ManagerReturnValue::GetAttributes ) } pub fn start_sign( &mut self, session: CK_SESSION_HANDLE, key_handle: CK_OBJECT_HANDLE, params: Option, ) -> Result<(), Error> { manager_proxy_fn_impl!( self, ManagerArguments::StartSign(session, key_handle, params), ManagerReturnValue::StartSign ) } pub fn get_signature_length( &self, session: CK_SESSION_HANDLE, data: Vec, ) -> Result { manager_proxy_fn_impl!( self, ManagerArguments::GetSignatureLength(session, data), ManagerReturnValue::GetSignatureLength ) } pub fn sign(&mut self, session: CK_SESSION_HANDLE, data: Vec) -> Result, Error> { manager_proxy_fn_impl!( self, ManagerArguments::Sign(session, data), ManagerReturnValue::Sign ) } pub fn stop(&mut self) -> Result<(), Error> { manager_proxy_fn_impl!(self, ManagerArguments::Stop, ManagerReturnValue::Stop)?; let thread_handle = match self.thread_handle.take() { Some(thread_handle) => thread_handle, None => return Err(error_here!(ErrorType::LibraryFailure)), }; thread_handle .join() .map_err(|_| error_here!(ErrorType::LibraryFailure)) } } // Determines if the attributes of a given search correspond to NSS looking for all certificates or // private keys. Returns true if so, and false otherwise. // These searches are of the form: // { { type: CKA_TOKEN, value: [1] }, // { type: CKA_CLASS, value: [CKO_CERTIFICATE or CKO_PRIVATE_KEY, as serialized bytes] } } // (although not necessarily in that order - see nssToken_TraverseCertificates and // nssToken_FindPrivateKeys) fn search_is_for_all_certificates_or_keys( attrs: &[(CK_ATTRIBUTE_TYPE, Vec)], ) -> Result { if attrs.len() != 2 { return Ok(false); } let token_bytes = vec![1_u8]; let mut found_token = false; let cko_certificate_bytes = serialize_uint(CKO_CERTIFICATE)?; let cko_private_key_bytes = serialize_uint(CKO_PRIVATE_KEY)?; let mut found_certificate_or_private_key = false; for (attr_type, attr_value) in attrs.iter() { if attr_type == &CKA_TOKEN && attr_value == &token_bytes { found_token = true; } if attr_type == &CKA_CLASS && (attr_value == &cko_certificate_bytes || attr_value == &cko_private_key_bytes) { found_certificate_or_private_key = true; } } Ok(found_token && found_certificate_or_private_key) } const SUPPORTED_ATTRIBUTES: &[CK_ATTRIBUTE_TYPE] = &[ CKA_CLASS, CKA_TOKEN, CKA_LABEL, CKA_ID, CKA_VALUE, CKA_ISSUER, CKA_SERIAL_NUMBER, CKA_SUBJECT, CKA_PRIVATE, CKA_KEY_TYPE, CKA_MODULUS, CKA_EC_PARAMS, ]; enum Object { Cert(B::Cert), Key(B::Key), } impl Object { fn matches(&self, slot_type: SlotType, attrs: &[(CK_ATTRIBUTE_TYPE, Vec)]) -> bool { match self { Object::Cert(cert) => cert.matches(slot_type, attrs), Object::Key(key) => key.matches(slot_type, attrs), } } fn get_attribute(&self, attribute: CK_ATTRIBUTE_TYPE) -> Option<&[u8]> { match self { Object::Cert(cert) => cert.get_attribute(attribute), Object::Key(key) => key.get_attribute(attribute), } } fn id(&self) -> Result<&[u8], Error> { self.get_attribute(CKA_ID) .ok_or_else(|| error_here!(ErrorType::LibraryFailure)) } fn get_signature_length( &mut self, data: Vec, params: &Option, ) -> Result { match self { Object::Cert(_) => Err(error_here!(ErrorType::InvalidArgument)), Object::Key(key) => key.get_signature_length(&data, params), } } fn sign( &mut self, data: Vec, params: &Option, ) -> Result, Error> { match self { Object::Cert(_) => Err(error_here!(ErrorType::InvalidArgument)), Object::Key(key) => key.sign(&data, params), } } } /// The `Manager` keeps track of the state of this module with respect to the PKCS #11 /// specification. This includes what sessions are open, which search and sign operations are /// ongoing, and what objects are known and by what handle. pub struct Manager { /// A map of session to session type (modern or legacy). Sessions can be created (opened) and /// later closed. sessions: BTreeMap, /// A map of searches to PKCS #11 object handles that match those searches. searches: BTreeMap>, /// A map of sign operations to a pair of the object handle and optionally some params being /// used by each one. signs: BTreeMap)>, /// A map of object handles to the underlying objects. objects: BTreeMap>, /// A set of certificate identifiers (not the same as handles). cert_ids: BTreeSet>, /// A set of key identifiers (not the same as handles). For each id in this set, there should be /// a corresponding identical id in the `cert_ids` set. key_ids: BTreeSet>, /// The next session handle to hand out. next_session: CK_SESSION_HANDLE, /// The next object handle to hand out. next_handle: CK_OBJECT_HANDLE, /// The last time the implementation looked for new objects in the backend. /// The implementation does this search no more than once every 3 seconds. last_scan_time: Option, backend: B, } impl Manager { pub fn new(backend: B) -> Manager { Manager { sessions: BTreeMap::new(), searches: BTreeMap::new(), signs: BTreeMap::new(), objects: BTreeMap::new(), cert_ids: BTreeSet::new(), key_ids: BTreeSet::new(), next_session: 1, next_handle: 1, last_scan_time: None, backend, } } /// When a new search session is opened (provided at least 3 seconds have elapsed since the /// last session was opened), this searches for certificates and keys to expose. We /// de-duplicate previously-found certificates and keys by keeping track of their IDs. fn maybe_find_new_objects(&mut self) -> Result<(), Error> { let now = Instant::now(); match self.last_scan_time { Some(last_scan_time) => { if now.duration_since(last_scan_time) < Duration::new(3, 0) { return Ok(()); } } None => {} } self.last_scan_time = Some(now); let (certs, keys) = self.backend.find_objects()?; for cert in certs { let object = Object::Cert(cert); if self.cert_ids.contains(object.id()?) { continue; } self.cert_ids.insert(object.id()?.to_vec()); let handle = self.get_next_handle(); self.objects.insert(handle, object); } for key in keys { let object = Object::Key(key); if self.key_ids.contains(object.id()?) { continue; } self.key_ids.insert(object.id()?.to_vec()); let handle = self.get_next_handle(); self.objects.insert(handle, object); } Ok(()) } pub fn open_session(&mut self, slot_type: SlotType) -> Result { let next_session = self.next_session; self.next_session += 1; self.sessions.insert(next_session, slot_type); Ok(next_session) } pub fn close_session(&mut self, session: CK_SESSION_HANDLE) -> Result<(), Error> { self.sessions .remove(&session) .ok_or_else(|| error_here!(ErrorType::InvalidInput)) .map(|_| ()) } pub fn close_all_sessions(&mut self, slot_type: SlotType) -> Result<(), Error> { let mut to_remove = Vec::new(); for (session, open_slot_type) in self.sessions.iter() { if slot_type == *open_slot_type { to_remove.push(*session); } } for session in to_remove { if self.sessions.remove(&session).is_none() { return Err(error_here!(ErrorType::LibraryFailure)); } } Ok(()) } fn get_next_handle(&mut self) -> CK_OBJECT_HANDLE { let next_handle = self.next_handle; self.next_handle += 1; next_handle } /// PKCS #11 specifies that search operations happen in three phases: setup, get any matches /// (this part may be repeated if the caller uses a small buffer), and end. This implementation /// does all of the work up front and gathers all matching objects during setup and retains them /// until they are retrieved and consumed via `search`. pub fn start_search( &mut self, session: CK_SESSION_HANDLE, attrs: Vec<(CK_ATTRIBUTE_TYPE, Vec)>, ) -> Result<(), Error> { let slot_type = match self.sessions.get(&session) { Some(slot_type) => *slot_type, None => return Err(error_here!(ErrorType::InvalidArgument)), }; // If the search is for an attribute we don't support, no objects will match. This check // saves us having to look through all of our objects. for (attr, _) in &attrs { if !SUPPORTED_ATTRIBUTES.contains(attr) { self.searches.insert(session, Vec::new()); return Ok(()); } } // When NSS wants to find all certificates or all private keys, it will perform a search // with a particular set of attributes. This implementation uses these searches as an // indication for the backend to re-scan for new objects from tokens that may have been // inserted or certificates that may have been imported into the OS. Since these searches // are relatively rare, this minimizes the impact of doing these re-scans. if search_is_for_all_certificates_or_keys(&attrs)? { self.maybe_find_new_objects()?; } let mut handles = Vec::new(); for (handle, object) in &self.objects { if object.matches(slot_type, &attrs) { handles.push(*handle); } } self.searches.insert(session, handles); Ok(()) } /// Given a session and a maximum number of object handles to return, attempts to retrieve up to /// that many objects from the corresponding search. Updates the search so those objects are not /// returned repeatedly. `max_objects` must be non-zero. pub fn search( &mut self, session: CK_SESSION_HANDLE, max_objects: usize, ) -> Result, Error> { if max_objects == 0 { return Err(error_here!(ErrorType::InvalidArgument)); } match self.searches.get_mut(&session) { Some(search) => { let split_at = if max_objects >= search.len() { 0 } else { search.len() - max_objects }; let to_return = search.split_off(split_at); if to_return.len() > max_objects { return Err(error_here!(ErrorType::LibraryFailure)); } Ok(to_return) } None => Err(error_here!(ErrorType::InvalidArgument)), } } pub fn clear_search(&mut self, session: CK_SESSION_HANDLE) -> Result<(), Error> { self.searches.remove(&session); Ok(()) } pub fn get_attributes( &self, object_handle: CK_OBJECT_HANDLE, attr_types: Vec, ) -> Result>>, Error> { let object = match self.objects.get(&object_handle) { Some(object) => object, None => return Err(error_here!(ErrorType::InvalidArgument)), }; let mut results = Vec::with_capacity(attr_types.len()); for attr_type in attr_types { let result = object .get_attribute(attr_type) .map(|value| value.to_owned()); results.push(result); } Ok(results) } /// The way NSS uses PKCS #11 to sign data happens in two phases: setup and sign. This /// implementation makes a note of which key is to be used (if it exists) during setup. When the /// caller finishes with the sign operation, this implementation retrieves the key handle and /// performs the signature. pub fn start_sign( &mut self, session: CK_SESSION_HANDLE, key_handle: CK_OBJECT_HANDLE, params: Option, ) -> Result<(), Error> { if self.signs.contains_key(&session) { return Err(error_here!(ErrorType::InvalidArgument)); } self.signs.insert(session, (key_handle, params)); Ok(()) } pub fn get_signature_length( &mut self, session: CK_SESSION_HANDLE, data: Vec, ) -> Result { let (key_handle, params) = match self.signs.get(&session) { Some((key_handle, params)) => (key_handle, params), None => return Err(error_here!(ErrorType::InvalidArgument)), }; let key = match self.objects.get_mut(key_handle) { Some(key) => key, None => return Err(error_here!(ErrorType::InvalidArgument)), }; key.get_signature_length(data, params) } pub fn sign(&mut self, session: CK_SESSION_HANDLE, data: Vec) -> Result, Error> { // Performing the signature (via C_Sign, which is the only way we support) finishes the sign // operation, so it needs to be removed here. let (key_handle, params) = match self.signs.remove(&session) { Some((key_handle, params)) => (key_handle, params), None => return Err(error_here!(ErrorType::InvalidArgument)), }; let key = match self.objects.get_mut(&key_handle) { Some(key) => key, None => return Err(error_here!(ErrorType::InvalidArgument)), }; key.sign(data, ¶ms) } }