/* -*- Mode: rust; rust-indent-offset: 4 -*- */ /* This Source Code Form is subject to the terms of the Mozilla Public *License,v.2.0.IfacopyoftheMPLwasnotdistributedwiththis
* 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};
/// 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)] pubenum SlotType {
Modern,
Legacy,
}
/// Helper type for sending `ManagerArguments` to the real `Manager`. type ManagerArgumentsSender = Sender<ManagerArguments>; /// Helper type for receiving `ManagerReturnValue`s from the real `Manager`. type ManagerReturnValueReceiver = Receiver<ManagerReturnValue>;
/// 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<u8>)>),
Search(CK_SESSION_HANDLE, usize),
ClearSearch(CK_SESSION_HANDLE),
GetAttributes(CK_OBJECT_HANDLE, Vec<CK_ATTRIBUTE_TYPE>),
StartSign(
CK_SESSION_HANDLE,
CK_OBJECT_HANDLE,
Option<CK_RSA_PKCS_PSS_PARAMS>,
),
GetSignatureLength(CK_SESSION_HANDLE, Vec<u8>),
Sign(CK_SESSION_HANDLE, Vec<u8>),
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<CK_SESSION_HANDLE, Error>),
CloseSession(Result<(), Error>),
CloseAllSessions(Result<(), Error>),
StartSearch(Result<(), Error>),
Search(Result<Vec<CK_OBJECT_HANDLE>, Error>),
ClearSearch(Result<(), Error>),
GetAttributes(Result<Vec<Option<Vec<u8>>>, Error>),
StartSign(Result<(), Error>),
GetSignatureLength(Result<usize, Error>),
Sign(Result<Vec<u8>, 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`. pubstruct ManagerProxy {
sender: ManagerArgumentsSender,
receiver: ManagerReturnValueReceiver,
thread_handle: Option<JoinHandle<()>>,
}
// 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<u8>)],
) -> Result<bool, Error> { if attrs.len() != 2 { return Ok(false);
} let token_bytes = vec![1_u8]; letmut found_token = false; let cko_certificate_bytes = serialize_uint(CKO_CERTIFICATE)?; let cko_private_key_bytes = serialize_uint(CKO_PRIVATE_KEY)?; letmut 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)
}
/// 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. pubstruct Manager<B: ClientCertsBackend> { /// A map of session to session type (modern or legacy). Sessions can be created (opened) and /// later closed.
sessions: BTreeMap<CK_SESSION_HANDLE, SlotType>, /// A map of searches to PKCS #11 object handles that match those searches.
searches: BTreeMap<CK_SESSION_HANDLE, Vec<CK_OBJECT_HANDLE>>, /// A map of sign operations to a pair of the object handle and optionally some params being /// used by each one.
signs: BTreeMap<CK_SESSION_HANDLE, (CK_OBJECT_HANDLE, Option<CK_RSA_PKCS_PSS_PARAMS>)>, /// A map of object handles to the underlying objects.
objects: BTreeMap<CK_OBJECT_HANDLE, Object<B>>, /// A set of certificate identifiers (not the same as handles).
cert_ids: BTreeSet<Vec<u8>>, /// 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<Vec<u8>>, /// 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<Instant>,
backend: B,
}
/// 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(&mutself) -> Result<(), Error> { let now = Instant::now(); matchself.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); ifself.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); ifself.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(())
}
/// 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`. pubfn start_search(
&mutself,
session: CK_SESSION_HANDLE,
attrs: Vec<(CK_ATTRIBUTE_TYPE, Vec<u8>)>,
) -> Result<(), Error> { let slot_type = matchself.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()?;
} letmut 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. pubfn search(
&mutself,
session: CK_SESSION_HANDLE,
max_objects: usize,
) -> Result<Vec<CK_OBJECT_HANDLE>, Error> { if max_objects == 0 { return Err(error_here!(ErrorType::InvalidArgument));
} matchself.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)),
}
}
pubfn get_attributes(
&self,
object_handle: CK_OBJECT_HANDLE,
attr_types: Vec<CK_ATTRIBUTE_TYPE>,
) -> Result<Vec<Option<Vec<u8>>>, Error> { let object = matchself.objects.get(&object_handle) {
Some(object) => object,
None => return Err(error_here!(ErrorType::InvalidArgument)),
}; letmut 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. pubfn start_sign(
&mutself,
session: CK_SESSION_HANDLE,
key_handle: CK_OBJECT_HANDLE,
params: Option<CK_RSA_PKCS_PSS_PARAMS>,
) -> Result<(), Error> { ifself.signs.contains_key(&session) { return Err(error_here!(ErrorType::InvalidArgument));
} self.signs.insert(session, (key_handle, params));
Ok(())
}
pubfn sign(&mutself, session: CK_SESSION_HANDLE, data: Vec<u8>) -> Result<Vec<u8>, 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) = matchself.signs.remove(&session) {
Some((key_handle, params)) => (key_handle, params),
None => return Err(error_here!(ErrorType::InvalidArgument)),
}; let key = matchself.objects.get_mut(&key_handle) {
Some(key) => key,
None => return Err(error_here!(ErrorType::InvalidArgument)),
};
key.sign(data, ¶ms)
}
}
Messung V0.5 in Prozent
¤ Dauer der Verarbeitung: 0.25 Sekunden
(vorverarbeitet am 2026-06-19)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.