/* -*- 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/. */
/// The singleton `ModuleState` that handles state with respect to PKCS #11. Only one thread /// may use it at a time, but there is no restriction on which threads may use it. However, as /// OS APIs being used are not necessarily thread-safe (e.g. they may be using /// thread-local-storage), the `ManagerProxy` of the `ModuleState` forwards calls from any /// thread to a single thread where the real `Manager` does the actual work. static MODULE_STATE: Mutex<Option<ModuleState>> = Mutex::new(None);
// Obtaining a handle on the manager proxy is a two-step process. First the mutex must be locked, // which (if successful), results in a mutex guard object. We must then get a mutable refence to the // underlying manager proxy (if set - otherwise we return an error). This can't happen all in one // macro without dropping a reference that needs to live long enough for this to be safe. In // practice, this looks like: // let mut module_state_guard = try_to_get_module_state_guard!(); // let manager = module_state_guard_to_manager!(module_state_guard);
macro_rules! try_to_get_module_state_guard {
() => { match MODULE_STATE.lock() {
Ok(maybe_module_state) => maybe_module_state,
Err(poison_error) => {
log_with_thread_id!(
error, "previous thread panicked acquiring manager lock: {}",
poison_error
); return CKR_DEVICE_ERROR;
}
}
};
}
macro_rules! module_state_guard_to_manager {
($module_state_guard:ident) => { match $module_state_guard.as_mut() {
Some(module_state) => &mut module_state.manager_proxy,
None => {
log_with_thread_id!(error, "module state expected to be set, but it is not"); return CKR_DEVICE_ERROR;
}
}
};
}
macro_rules! module_state_guard_to_mechanisms {
($module_state_guard:ident) => { match $module_state_guard.as_ref() {
Some(module_state) => &module_state.mechanisms,
None => {
log_with_thread_id!(error, "module state expected to be set, but it is not"); return CKR_DEVICE_ERROR;
}
}
};
}
// Helper macro to prefix log messages with the current thread ID.
macro_rules! log_with_thread_id {
($log_level:ident, $($message:expr),*) => {
$log_level!("{:?} {}", thread::current().id(), format_args!($($message),*));
};
}
/// This gets called to initialize the module. For this implementation, this consists of /// instantiating the `ManagerProxy`. extern"C"fn C_Initialize(_pInitArgs: CK_VOID_PTR) -> CK_RV { // This will fail if this has already been called, but this isn't a problem because either way, // logging has been initialized. let _ = env_logger::try_init();
let mechanisms = if static_prefs::pref!("security.osclientcerts.assume_rsa_pss_support") {
vec![CKM_ECDSA, CKM_RSA_PKCS, CKM_RSA_PKCS_PSS]
} else {
vec![CKM_ECDSA, CKM_RSA_PKCS]
}; letmut module_state_guard = try_to_get_module_state_guard!(); let manager_proxy = match ManagerProxy::new(Backend {}) {
Ok(p) => p,
Err(e) => {
log_with_thread_id!(error, "C_Initialize: ManagerProxy: {}", e); return CKR_DEVICE_ERROR;
}
}; match module_state_guard.replace(ModuleState {
manager_proxy,
mechanisms,
}) {
Some(_unexpected_previous_module_state) => {
log_with_thread_id!(
warn, "C_Initialize: replacing previously set module state (this is expected on macOS but not on Windows)"
);
}
None => {}
}
log_with_thread_id!(debug, "C_Initialize: CKR_OK");
CKR_OK
}
// The specification mandates that these strings be padded with spaces to the appropriate length. // Since the length of fixed-size arrays in rust is part of the type, the compiler enforces that // these byte strings are of the correct length. const MANUFACTURER_ID_BYTES: &[u8; 32] = b"Mozilla Corporation "; const LIBRARY_DESCRIPTION_BYTES: &[u8; 32] = b"OS Client Cert Module ";
/// This gets called to gather some information about the module. In particular, this implementation /// supports (portions of) cryptoki (PKCS #11) version 2.2. extern"C"fn C_GetInfo(pInfo: CK_INFO_PTR) -> CK_RV { if pInfo.is_null() {
log_with_thread_id!(error, "C_GetInfo: CKR_ARGUMENTS_BAD"); return CKR_ARGUMENTS_BAD;
}
log_with_thread_id!(debug, "C_GetInfo: CKR_OK"); letmut info = CK_INFO::default();
info.cryptokiVersion.major = 2;
info.cryptokiVersion.minor = 2;
info.manufacturerID = *MANUFACTURER_ID_BYTES;
info.libraryDescription = *LIBRARY_DESCRIPTION_BYTES; unsafe {
*pInfo = info;
}
CKR_OK
}
/// This module has one slot. const SLOT_COUNT: CK_ULONG = 1; const SLOT_ID: CK_SLOT_ID = 1;
/// This gets called twice: once with a null `pSlotList` to get the number of slots (returned via /// `pulCount`) and a second time to get the ID for each slot. extern"C"fn C_GetSlotList(
_tokenPresent: CK_BBOOL,
pSlotList: CK_SLOT_ID_PTR,
pulCount: CK_ULONG_PTR,
) -> CK_RV { if pulCount.is_null() {
log_with_thread_id!(error, "C_GetSlotList: CKR_ARGUMENTS_BAD"); return CKR_ARGUMENTS_BAD;
} if !pSlotList.is_null() { ifunsafe { *pulCount } < SLOT_COUNT {
log_with_thread_id!(error, "C_GetSlotList: CKR_BUFFER_TOO_SMALL"); return CKR_BUFFER_TOO_SMALL;
} unsafe {
*pSlotList = SLOT_ID;
}
}; unsafe {
*pulCount = SLOT_COUNT;
}
log_with_thread_id!(debug, "C_GetSlotList: CKR_OK");
CKR_OK
}
/// This gets called to obtain some information about tokens. This implementation has one slot, /// so it has one token. This information is primarily for display purposes. extern"C"fn C_GetTokenInfo(slotID: CK_SLOT_ID, pInfo: CK_TOKEN_INFO_PTR) -> CK_RV { if slotID != SLOT_ID || pInfo.is_null() {
log_with_thread_id!(error, "C_GetTokenInfo: CKR_ARGUMENTS_BAD"); return CKR_ARGUMENTS_BAD;
} letmut token_info = CK_TOKEN_INFO::default();
token_info.label = *TOKEN_LABEL_BYTES;
token_info.manufacturerID = *MANUFACTURER_ID_BYTES;
token_info.model = *TOKEN_MODEL_BYTES;
token_info.serialNumber = *TOKEN_SERIAL_NUMBER_BYTES; unsafe {
*pInfo = token_info;
}
log_with_thread_id!(debug, "C_GetTokenInfo: CKR_OK");
CKR_OK
}
/// This gets called to determine what mechanisms a slot supports. The singular slot supports /// ECDSA and RSA PKCS1. Depending on the configuration the module was loaded with, it may also /// support RSA PSS. extern"C"fn C_GetMechanismList(
slotID: CK_SLOT_ID,
pMechanismList: CK_MECHANISM_TYPE_PTR,
pulCount: CK_ULONG_PTR,
) -> CK_RV { if slotID != SLOT_ID || pulCount.is_null() {
log_with_thread_id!(error, "C_GetMechanismList: CKR_ARGUMENTS_BAD"); return CKR_ARGUMENTS_BAD;
} let module_state_guard = try_to_get_module_state_guard!(); let mechanisms = module_state_guard_to_mechanisms!(module_state_guard); if !pMechanismList.is_null() { ifunsafe { *pulCount as usize } < mechanisms.len() {
log_with_thread_id!(error, "C_GetMechanismList: CKR_ARGUMENTS_BAD"); return CKR_ARGUMENTS_BAD;
} for (i, mechanism) in mechanisms.iter().enumerate() { unsafe {
*pMechanismList.add(i) = *mechanism;
}
}
} unsafe {
*pulCount = mechanisms.len() as CK_ULONG;
}
log_with_thread_id!(debug, "C_GetMechanismList: CKR_OK");
CKR_OK
}
/// This gets called to create a new session. This module defers to the `ManagerProxy` to implement /// this. extern"C"fn C_OpenSession(
slotID: CK_SLOT_ID,
_flags: CK_FLAGS,
_pApplication: CK_VOID_PTR,
_Notify: CK_NOTIFY,
phSession: CK_SESSION_HANDLE_PTR,
) -> CK_RV { if slotID != SLOT_ID || phSession.is_null() {
log_with_thread_id!(error, "C_OpenSession: CKR_ARGUMENTS_BAD"); return CKR_ARGUMENTS_BAD;
} letmut module_state_guard = try_to_get_module_state_guard!(); let manager = module_state_guard_to_manager!(module_state_guard); // The "modern"/"legacy" slot distinction still exists in ipcclientcerts, // which shares some library code with this module, to allow for a more // nuanced notion of whether or not e.g. RSA-PSS is supported. let session_handle = match manager.open_session(SlotType::Modern) {
Ok(session_handle) => session_handle,
Err(e) => {
log_with_thread_id!(error, "C_OpenSession: open_session failed: {}", e); return CKR_DEVICE_ERROR;
}
}; unsafe {
*phSession = session_handle;
}
log_with_thread_id!(debug, "C_OpenSession: CKR_OK");
CKR_OK
}
/// This gets called to close a session. This is handled by the `ManagerProxy`. extern"C"fn C_CloseSession(hSession: CK_SESSION_HANDLE) -> CK_RV { letmut module_state_guard = try_to_get_module_state_guard!(); let manager = module_state_guard_to_manager!(module_state_guard); if manager.close_session(hSession).is_err() {
log_with_thread_id!(error, "C_CloseSession: CKR_SESSION_HANDLE_INVALID"); return CKR_SESSION_HANDLE_INVALID;
}
log_with_thread_id!(debug, "C_CloseSession: CKR_OK");
CKR_OK
}
/// This gets called to close all open sessions at once. This is handled by the `ManagerProxy`. extern"C"fn C_CloseAllSessions(slotID: CK_SLOT_ID) -> CK_RV { if slotID != SLOT_ID {
log_with_thread_id!(error, "C_CloseAllSessions: CKR_ARGUMENTS_BAD"); return CKR_ARGUMENTS_BAD;
} letmut module_state_guard = try_to_get_module_state_guard!(); let manager = module_state_guard_to_manager!(module_state_guard); match manager.close_all_sessions(SlotType::Modern) {
Ok(()) => {
log_with_thread_id!(debug, "C_CloseAllSessions: CKR_OK");
CKR_OK
}
Err(e) => {
log_with_thread_id!(
error, "C_CloseAllSessions: close_all_sessions failed: {}",
e
);
CKR_DEVICE_ERROR
}
}
}
/// This gets called to log out and drop any authenticated resources. Because this module does not /// hold on to authenticated resources, this module "implements" this by doing nothing and /// returning a success result. extern"C"fn C_Logout(_hSession: CK_SESSION_HANDLE) -> CK_RV {
log_with_thread_id!(debug, "C_Logout: CKR_OK");
CKR_OK
}
/// This gets called to obtain the values of a number of attributes of an object identified by the /// given handle. This module implements this by requesting that the `ManagerProxy` find the object /// and attempt to get the value of each attribute. If a specified attribute is not defined on the /// object, the length of that attribute is set to -1 to indicate that it is not available. /// This gets called twice: once to obtain the lengths of the attributes and again to get the /// values. extern"C"fn C_GetAttributeValue(
_hSession: CK_SESSION_HANDLE,
hObject: CK_OBJECT_HANDLE,
pTemplate: CK_ATTRIBUTE_PTR,
ulCount: CK_ULONG,
) -> CK_RV { if pTemplate.is_null() {
log_with_thread_id!(error, "C_GetAttributeValue: CKR_ARGUMENTS_BAD"); return CKR_ARGUMENTS_BAD;
} letmut attr_types = Vec::with_capacity(ulCount as usize); for i in0..ulCount as usize { let attr = unsafe { &*pTemplate.add(i) };
attr_types.push(attr.type_);
} letmut module_state_guard = try_to_get_module_state_guard!(); let manager = module_state_guard_to_manager!(module_state_guard); let values = match manager.get_attributes(hObject, attr_types) {
Ok(values) => values,
Err(e) => {
log_with_thread_id!(error, "C_GetAttributeValue: CKR_ARGUMENTS_BAD ({})", e); return CKR_ARGUMENTS_BAD;
}
}; if values.len() != ulCount as usize {
log_with_thread_id!(
error, "C_GetAttributeValue: manager.get_attributes didn't return the right number of values"
); return CKR_DEVICE_ERROR;
} for (i, value) in values.iter().enumerate().take(ulCount as usize) { let attr = unsafe { &mut *pTemplate.add(i) }; iflet Some(attr_value) = value { if attr.pValue.is_null() {
attr.ulValueLen = attr_value.len() as CK_ULONG;
} else { let ptr: *mut u8 = attr.pValue as *mut u8; if attr_value.len() != attr.ulValueLen as usize {
log_with_thread_id!(error, "C_GetAttributeValue: incorrect attr size"); return CKR_ARGUMENTS_BAD;
} unsafe {
std::ptr::copy_nonoverlapping(attr_value.as_ptr(), ptr, attr_value.len());
}
}
} else {
attr.ulValueLen = (0 - 1) as CK_ULONG;
}
}
log_with_thread_id!(debug, "C_GetAttributeValue: CKR_OK");
CKR_OK
}
/// This gets called to initialize a search for objects matching a given list of attributes. This /// module implements this by gathering the attributes and passing them to the `ManagerProxy` to /// start the search. extern"C"fn C_FindObjectsInit(
hSession: CK_SESSION_HANDLE,
pTemplate: CK_ATTRIBUTE_PTR,
ulCount: CK_ULONG,
) -> CK_RV { if pTemplate.is_null() {
log_with_thread_id!(error, "C_FindObjectsInit: CKR_ARGUMENTS_BAD"); return CKR_ARGUMENTS_BAD;
} letmut attrs = Vec::new();
log_with_thread_id!(trace, "C_FindObjectsInit:"); for i in0..ulCount as usize { let attr = unsafe { &*pTemplate.add(i) };
trace_attr(" ", attr); // Copy out the attribute type to avoid making a reference to an unaligned field. let attr_type = attr.type_; if !RELEVANT_ATTRIBUTES.contains(&attr_type) {
log_with_thread_id!(
debug, "C_FindObjectsInit: irrelevant attribute, returning CKR_ATTRIBUTE_TYPE_INVALID"
); return CKR_ATTRIBUTE_TYPE_INVALID;
} let slice = unsafe {
std::slice::from_raw_parts(attr.pValue as *const u8, attr.ulValueLen as usize)
};
attrs.push((attr_type, slice.to_owned()));
} letmut module_state_guard = try_to_get_module_state_guard!(); let manager = module_state_guard_to_manager!(module_state_guard); match manager.start_search(hSession, attrs) {
Ok(()) => {}
Err(e) => {
log_with_thread_id!(error, "C_FindObjectsInit: CKR_ARGUMENTS_BAD: {}", e); return CKR_ARGUMENTS_BAD;
}
}
log_with_thread_id!(debug, "C_FindObjectsInit: CKR_OK");
CKR_OK
}
/// This gets called after `C_FindObjectsInit` to get the results of a search. This module /// implements this by looking up the search in the `ManagerProxy` and copying out the matching /// object handles. extern"C"fn C_FindObjects(
hSession: CK_SESSION_HANDLE,
phObject: CK_OBJECT_HANDLE_PTR,
ulMaxObjectCount: CK_ULONG,
pulObjectCount: CK_ULONG_PTR,
) -> CK_RV { if phObject.is_null() || pulObjectCount.is_null() || ulMaxObjectCount == 0 {
log_with_thread_id!(error, "C_FindObjects: CKR_ARGUMENTS_BAD"); return CKR_ARGUMENTS_BAD;
} letmut module_state_guard = try_to_get_module_state_guard!(); let manager = module_state_guard_to_manager!(module_state_guard); let handles = match manager.search(hSession, ulMaxObjectCount as usize) {
Ok(handles) => handles,
Err(e) => {
log_with_thread_id!(error, "C_FindObjects: CKR_ARGUMENTS_BAD: {}", e); return CKR_ARGUMENTS_BAD;
}
};
log_with_thread_id!(debug, "C_FindObjects: found handles {:?}", handles); if handles.len() > ulMaxObjectCount as usize {
log_with_thread_id!(error, "C_FindObjects: manager returned too many handles"); return CKR_DEVICE_ERROR;
} unsafe {
*pulObjectCount = handles.len() as CK_ULONG;
} for (index, handle) in handles.iter().enumerate() { if index < ulMaxObjectCount as usize { unsafe {
*(phObject.add(index)) = *handle;
}
}
}
log_with_thread_id!(debug, "C_FindObjects: CKR_OK");
CKR_OK
}
/// This gets called after `C_FindObjectsInit` and `C_FindObjects` to finish a search. The module /// tells the `ManagerProxy` to clear the search. extern"C"fn C_FindObjectsFinal(hSession: CK_SESSION_HANDLE) -> CK_RV { letmut module_state_guard = try_to_get_module_state_guard!(); let manager = module_state_guard_to_manager!(module_state_guard); // It would be an error if there were no search for this session, but we can be permissive here. match manager.clear_search(hSession) {
Ok(()) => {
log_with_thread_id!(debug, "C_FindObjectsFinal: CKR_OK");
CKR_OK
}
Err(e) => {
log_with_thread_id!(error, "C_FindObjectsFinal: clear_search failed: {}", e);
CKR_DEVICE_ERROR
}
}
}
/// # Safety /// /// This is the only function this module exposes. NSS calls it to obtain the list of functions /// comprising this module. /// ppFunctionList must be a valid pointer. #[no_mangle] pubunsafeextern"C"fn OSClientCerts_C_GetFunctionList(
ppFunctionList: CK_FUNCTION_LIST_PTR_PTR,
) -> CK_RV { if ppFunctionList.is_null() { return CKR_ARGUMENTS_BAD;
} // CK_FUNCTION_LIST_PTR is a *mut CK_FUNCTION_LIST, but as per the // specification, the caller must treat it as *const CK_FUNCTION_LIST.
*ppFunctionList = std::ptr::addr_of!(FUNCTION_LIST) as CK_FUNCTION_LIST_PTR;
CKR_OK
}
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.