Bug 1962280 - osclientcerts: avoid unnecessary background thread dispatches r=jschanck
On macOS and Windows, the underlying implementations of the OS APIs that provide access to keys and certificates are not necessarily thread-safe. Before this patch, essentially all calls into osclientcerts would be synchronously forwarded to a single background thread. Unfortunately, the cost of dispatching and waiting for all of these events was non-trivial, particularly when the vast majority of them should essentially be no-ops (e.g. when NSS is looking for CA certificates or trust information, etc.). This patch reworks the implementation to only dispatch operations involving OS APIs to the background thread. Differential Revision: https://phabricator.services.mozilla.com/D246520
This commit is contained in:
4
Cargo.lock
generated
4
Cargo.lock
generated
@@ -4972,14 +4972,16 @@ dependencies = [
|
||||
"byteorder",
|
||||
"core-foundation 0.9.999",
|
||||
"env_logger",
|
||||
"futures-executor",
|
||||
"lazy_static",
|
||||
"libloading",
|
||||
"log",
|
||||
"moz_task",
|
||||
"pkcs11-bindings",
|
||||
"rsclientcerts",
|
||||
"sha2",
|
||||
"static_prefs",
|
||||
"winapi",
|
||||
"xpcom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -102,11 +102,6 @@ bool LoadOSClientCertsModule();
|
||||
*/
|
||||
bool LoadIPCClientCertsModule();
|
||||
|
||||
/**
|
||||
* Unloads the loadable roots module and os client certs module, if loaded.
|
||||
*/
|
||||
void UnloadUserModules();
|
||||
|
||||
nsresult DefaultServerNicknameForCert(const CERTCertificate* cert,
|
||||
/*out*/ nsCString& nickname);
|
||||
|
||||
|
||||
@@ -1618,8 +1618,15 @@ void nsNSSComponent::PrepareForShutdown() {
|
||||
|
||||
// Release the default CertVerifier. This will cause any held NSS resources
|
||||
// to be released.
|
||||
{
|
||||
MutexAutoLock lock(mMutex);
|
||||
mDefaultCertVerifier = nullptr;
|
||||
}
|
||||
|
||||
// Unload osclientcerts so it drops any held resources and stops its
|
||||
// background thread.
|
||||
AsyncLoadOrUnloadOSClientCertsModule(false);
|
||||
|
||||
// We don't actually shut down NSS - XPCOM does, after all threads have been
|
||||
// joined and the component manager has been shut down (and so there shouldn't
|
||||
// be any XPCOM objects holding NSS resources).
|
||||
|
||||
@@ -9,13 +9,14 @@ license = "MPL-2.0"
|
||||
[dependencies]
|
||||
byteorder = "1.3"
|
||||
env_logger = {version = "0.10", default-features = false } # disable `regex` to reduce code size
|
||||
futures-executor = { version = "0.3" }
|
||||
lazy_static = "1"
|
||||
log = "0.4"
|
||||
|
||||
moz_task = { path = "../../../../xpcom/rust/moz_task" }
|
||||
pkcs11-bindings = "0.1"
|
||||
rsclientcerts = { path = "../rsclientcerts" }
|
||||
sha2 = "0.10.2"
|
||||
static_prefs = { path = "../../../../modules/libpref/init/static_prefs"}
|
||||
xpcom = { path = "../../../../xpcom/rust/xpcom" }
|
||||
|
||||
[target."cfg(any(target_os = \"macos\", target_os = \"ios\"))".dependencies.core-foundation]
|
||||
version = "0.9"
|
||||
|
||||
@@ -7,7 +7,7 @@ use pkcs11_bindings::*;
|
||||
use rsclientcerts::error::{Error, ErrorType};
|
||||
use rsclientcerts::manager::{ClientCertsBackend, CryptokiObject, Sign};
|
||||
use rsclientcerts::util::*;
|
||||
use sha2::{Sha256, Digest};
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::ffi::{c_char, c_void, CString};
|
||||
|
||||
type FindObjectsCallback = Option<
|
||||
@@ -184,11 +184,7 @@ pub struct Key {
|
||||
}
|
||||
|
||||
impl Key {
|
||||
fn new(
|
||||
modulus: Option<&[u8]>,
|
||||
ec_params: Option<&[u8]>,
|
||||
cert: &[u8],
|
||||
) -> Result<Key, Error> {
|
||||
fn new(modulus: Option<&[u8]>, ec_params: Option<&[u8]>, cert: &[u8]) -> Result<Key, Error> {
|
||||
let id = Sha256::digest(cert).to_vec();
|
||||
let key_type = if modulus.is_some() { CKK_RSA } else { CKK_EC };
|
||||
// If this is an EC key, the frontend will have provided an SPKI.
|
||||
@@ -413,6 +409,12 @@ impl FindObjectsContext {
|
||||
|
||||
pub struct Backend {}
|
||||
|
||||
impl Backend {
|
||||
pub fn new() -> Result<Backend, Error> {
|
||||
Ok(Backend {})
|
||||
}
|
||||
}
|
||||
|
||||
impl ClientCertsBackend for Backend {
|
||||
type Cert = Cert;
|
||||
type Key = Key;
|
||||
|
||||
@@ -22,6 +22,8 @@ use sha2::{Digest, Sha256};
|
||||
use std::collections::BTreeMap;
|
||||
use std::convert::TryInto;
|
||||
use std::os::raw::c_void;
|
||||
use xpcom::interfaces::nsIEventTarget;
|
||||
use xpcom::{RefPtr, XpCom};
|
||||
|
||||
// Normally we would generate this with a build script, but macos is
|
||||
// cross-compiled on linux, and we'd have to figure out e.g. include paths,
|
||||
@@ -34,6 +36,12 @@ pub type SecIdentityRef = *const __SecIdentity;
|
||||
declare_TCFType!(SecIdentity, SecIdentityRef);
|
||||
impl_TCFType!(SecIdentity, SecIdentityRef, SecIdentityGetTypeID);
|
||||
|
||||
/// Safety: strictly speaking, it isn't safe to send `SecIdentity` across threads. The
|
||||
/// implementation handles this by wrapping `SecIdentity` in `ThreadSpecificHandles`. However, in
|
||||
/// order to implement `Drop` for `ThreadSpecificHandles`, the `SecIdentity` it holds must be sent
|
||||
/// to the appropriate thread, hence this impl.
|
||||
unsafe impl Send for SecIdentity {}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct __SecCertificate(c_void);
|
||||
pub type SecCertificateRef = *const __SecCertificate;
|
||||
@@ -46,6 +54,9 @@ pub type SecKeyRef = *const __SecKey;
|
||||
declare_TCFType!(SecKey, SecKeyRef);
|
||||
impl_TCFType!(SecKey, SecKeyRef, SecKeyGetTypeID);
|
||||
|
||||
/// Safety: see the comment for the `Send` impl for `SecIdentity`.
|
||||
unsafe impl Send for SecKey {}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct __SecPolicy(c_void);
|
||||
pub type SecPolicyRef = *const __SecPolicy;
|
||||
@@ -492,8 +503,141 @@ impl<'a> SignParams<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper struct to hold onto OS-specific handles that must only be used on a particular thread.
|
||||
struct ThreadSpecificHandles {
|
||||
/// The only thread that these handles may be used on.
|
||||
thread: RefPtr<nsIEventTarget>,
|
||||
/// OS handle on a certificate and corresponding private key.
|
||||
identity: Option<SecIdentity>,
|
||||
/// OS handle on a private key.
|
||||
key: Option<SecKey>,
|
||||
}
|
||||
|
||||
impl ThreadSpecificHandles {
|
||||
fn new(identity: SecIdentity, thread: &nsIEventTarget) -> ThreadSpecificHandles {
|
||||
ThreadSpecificHandles {
|
||||
thread: RefPtr::new(thread),
|
||||
identity: Some(identity),
|
||||
key: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn sign(
|
||||
&mut self,
|
||||
key_type_enum: KeyType,
|
||||
maybe_modulus: Option<Vec<u8>>,
|
||||
data: &[u8],
|
||||
params: &Option<CK_RSA_PKCS_PSS_PARAMS>,
|
||||
) -> Result<Vec<u8>, Error> {
|
||||
let Some(identity) = self.identity.take() else {
|
||||
return Err(error_here!(ErrorType::LibraryFailure));
|
||||
};
|
||||
let mut maybe_key = self.key.take();
|
||||
let thread = self.thread.clone();
|
||||
let data = data.to_vec();
|
||||
let params = params.clone();
|
||||
let task = moz_task::spawn_onto("sign", &thread, async move {
|
||||
let result = sign_internal(&identity, &mut maybe_key, key_type_enum, &data, ¶ms);
|
||||
if result.is_ok() {
|
||||
return (result, identity, maybe_key);
|
||||
}
|
||||
// Some devices appear to not work well when the key handle is held for too long or if a
|
||||
// card is inserted/removed while Firefox is running. Try refreshing the key handle.
|
||||
let _ = maybe_key.take();
|
||||
let result = sign_internal(&identity, &mut maybe_key, key_type_enum, &data, ¶ms);
|
||||
// If this succeeded, return the result.
|
||||
if result.is_ok() {
|
||||
return (result, identity, maybe_key);
|
||||
}
|
||||
// If signing failed and this is an RSA-PSS signature, perhaps the token the key is on does
|
||||
// not support RSA-PSS. In that case, emsa-pss-encode the data (hash, really) and try
|
||||
// signing with raw RSA.
|
||||
let Some(params) = params.as_ref() else {
|
||||
return (result, identity, maybe_key);
|
||||
};
|
||||
// `params` should only be `Some` if this is an RSA key.
|
||||
let Some(modulus) = maybe_modulus.as_ref() else {
|
||||
return (
|
||||
Err(error_here!(ErrorType::LibraryFailure)),
|
||||
identity,
|
||||
maybe_key,
|
||||
);
|
||||
};
|
||||
let emsa_pss_encoded =
|
||||
match emsa_pss_encode(&data, modulus_bit_length(modulus) - 1, ¶ms) {
|
||||
Ok(emsa_pss_encoded) => emsa_pss_encoded,
|
||||
Err(e) => return (Err(e), identity, maybe_key),
|
||||
};
|
||||
(
|
||||
sign_internal(
|
||||
&identity,
|
||||
&mut maybe_key,
|
||||
key_type_enum,
|
||||
&emsa_pss_encoded,
|
||||
&None,
|
||||
),
|
||||
identity,
|
||||
maybe_key,
|
||||
)
|
||||
});
|
||||
let (signature_result, identity, maybe_key) = futures_executor::block_on(task);
|
||||
self.identity = Some(identity);
|
||||
self.key = maybe_key;
|
||||
signature_result
|
||||
}
|
||||
}
|
||||
|
||||
fn sign_internal(
|
||||
identity: &SecIdentity,
|
||||
maybe_key: &mut Option<SecKey>,
|
||||
key_type_enum: KeyType,
|
||||
data: &[u8],
|
||||
params: &Option<CK_RSA_PKCS_PSS_PARAMS>,
|
||||
) -> Result<Vec<u8>, Error> {
|
||||
// If this key hasn't been used for signing yet, there won't be a cached key handle. Obtain
|
||||
// and cache it if this is the case. Doing so can cause the underlying implementation to
|
||||
// show an authentication or pin prompt to the user. Caching the handle can avoid causing
|
||||
// multiple prompts to be displayed in some cases.
|
||||
if maybe_key.is_none() {
|
||||
let _ = maybe_key.replace(sec_identity_copy_private_key(identity)?);
|
||||
}
|
||||
let Some(key) = maybe_key.as_ref() else {
|
||||
return Err(error_here!(ErrorType::LibraryFailure));
|
||||
};
|
||||
let sign_params = SignParams::new(key_type_enum, data, params)?;
|
||||
let signing_algorithm = sign_params.get_algorithm();
|
||||
let data_to_sign = CFData::from_buffer(sign_params.get_data_to_sign());
|
||||
let signature = sec_key_create_signature(key, signing_algorithm, &data_to_sign)?;
|
||||
let signature_value = match key_type_enum {
|
||||
KeyType::EC(coordinate_width) => {
|
||||
// We need to convert the DER Ecdsa-Sig-Value to the
|
||||
// concatenation of r and s, the coordinates of the point on
|
||||
// the curve. r and s must be 0-padded to be coordinate_width
|
||||
// total bytes.
|
||||
der_ec_sig_to_raw(signature.bytes(), coordinate_width)?
|
||||
}
|
||||
KeyType::RSA => signature.bytes().to_vec(),
|
||||
};
|
||||
Ok(signature_value)
|
||||
}
|
||||
|
||||
impl Drop for ThreadSpecificHandles {
|
||||
fn drop(&mut self) {
|
||||
// Ensure any OS handles are dropped on the appropriate thread.
|
||||
let identity = self.identity.take();
|
||||
let key = self.key.take();
|
||||
let thread = self.thread.clone();
|
||||
let task = moz_task::spawn_onto("drop", &thread, async move {
|
||||
// `key` is obtained from `identity`, so drop it first, out of an abundance of caution.
|
||||
drop(key);
|
||||
drop(identity);
|
||||
});
|
||||
futures_executor::block_on(task)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Key {
|
||||
identity: SecIdentity,
|
||||
handles: ThreadSpecificHandles,
|
||||
class: Vec<u8>,
|
||||
token: Vec<u8>,
|
||||
id: Vec<u8>,
|
||||
@@ -502,11 +646,10 @@ pub struct Key {
|
||||
modulus: Option<Vec<u8>>,
|
||||
ec_params: Option<Vec<u8>>,
|
||||
key_type_enum: KeyType,
|
||||
key_handle: Option<SecKey>,
|
||||
}
|
||||
|
||||
impl Key {
|
||||
fn new(identity: &SecIdentity) -> Result<Key, Error> {
|
||||
fn new(identity: &SecIdentity, thread: &nsIEventTarget) -> Result<Key, Error> {
|
||||
let certificate = sec_identity_copy_certificate(identity)?;
|
||||
let der = sec_certificate_copy_data(&certificate)?;
|
||||
let id = Sha256::digest(der.bytes()).to_vec();
|
||||
@@ -546,7 +689,7 @@ impl Key {
|
||||
};
|
||||
|
||||
Ok(Key {
|
||||
identity: identity.clone(),
|
||||
handles: ThreadSpecificHandles::new(identity.clone(), thread),
|
||||
class: serialize_uint(CKO_PRIVATE_KEY)?,
|
||||
token: serialize_uint(CK_TRUE)?,
|
||||
id,
|
||||
@@ -555,7 +698,6 @@ impl Key {
|
||||
modulus,
|
||||
ec_params,
|
||||
key_type_enum,
|
||||
key_handle: None,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -592,41 +734,6 @@ impl Key {
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn sign_internal(
|
||||
&mut self,
|
||||
data: &[u8],
|
||||
params: &Option<CK_RSA_PKCS_PSS_PARAMS>,
|
||||
) -> Result<Vec<u8>, Error> {
|
||||
// If this key hasn't been used for signing yet, there won't be a cached key handle. Obtain
|
||||
// and cache it if this is the case. Doing so can cause the underlying implementation to
|
||||
// show an authentication or pin prompt to the user. Caching the handle can avoid causing
|
||||
// multiple prompts to be displayed in some cases.
|
||||
if self.key_handle.is_none() {
|
||||
let _ = self
|
||||
.key_handle
|
||||
.replace(sec_identity_copy_private_key(&self.identity)?);
|
||||
}
|
||||
let key = match &self.key_handle {
|
||||
Some(key) => key,
|
||||
None => return Err(error_here!(ErrorType::LibraryFailure)),
|
||||
};
|
||||
let sign_params = SignParams::new(self.key_type_enum, data, params)?;
|
||||
let signing_algorithm = sign_params.get_algorithm();
|
||||
let data_to_sign = CFData::from_buffer(sign_params.get_data_to_sign());
|
||||
let signature = sec_key_create_signature(key, signing_algorithm, &data_to_sign)?;
|
||||
let signature_value = match self.key_type_enum {
|
||||
KeyType::EC(coordinate_width) => {
|
||||
// We need to convert the DER Ecdsa-Sig-Value to the
|
||||
// concatenation of r and s, the coordinates of the point on
|
||||
// the curve. r and s must be 0-padded to be coordinate_width
|
||||
// total bytes.
|
||||
der_ec_sig_to_raw(signature.bytes(), coordinate_width)?
|
||||
}
|
||||
KeyType::RSA => signature.bytes().to_vec(),
|
||||
};
|
||||
Ok(signature_value)
|
||||
}
|
||||
}
|
||||
|
||||
impl CryptokiObject for Key {
|
||||
@@ -693,30 +800,8 @@ impl Sign for Key {
|
||||
data: &[u8],
|
||||
params: &Option<CK_RSA_PKCS_PSS_PARAMS>,
|
||||
) -> Result<Vec<u8>, Error> {
|
||||
let result = self.sign_internal(data, params);
|
||||
if result.is_ok() {
|
||||
return result;
|
||||
}
|
||||
// Some devices appear to not work well when the key handle is held for too long or if a
|
||||
// card is inserted/removed while Firefox is running. Try refreshing the key handle.
|
||||
let _ = self.key_handle.take();
|
||||
let result = self.sign_internal(data, params);
|
||||
// If this succeeded, return the result.
|
||||
if result.is_ok() {
|
||||
return result;
|
||||
}
|
||||
// If signing failed and this is an RSA-PSS signature, perhaps the token the key is on does
|
||||
// not support RSA-PSS. In that case, emsa-pss-encode the data (hash, really) and try
|
||||
// signing with raw RSA.
|
||||
let Some(params) = params.as_ref() else {
|
||||
return result;
|
||||
};
|
||||
// `params` should only be `Some` if this is an RSA key.
|
||||
let Some(modulus) = self.modulus.as_ref() else {
|
||||
return Err(error_here!(ErrorType::LibraryFailure));
|
||||
};
|
||||
let emsa_pss_encoded = emsa_pss_encode(data, modulus_bit_length(modulus) - 1, params)?;
|
||||
self.sign_internal(&emsa_pss_encoded, &None)
|
||||
self.handles
|
||||
.sign(self.key_type_enum, self.modulus.clone(), data, params)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -783,13 +868,39 @@ fn get_issuers(identity: &SecIdentity) -> Result<Vec<SecCertificate>, Error> {
|
||||
Ok(certificates)
|
||||
}
|
||||
|
||||
pub struct Backend {}
|
||||
pub struct Backend {
|
||||
/// A background thread that all OS API calls will be done on. This is to prevent issues with
|
||||
/// modules or implementations using thread-local state.
|
||||
thread: RefPtr<nsIEventTarget>,
|
||||
}
|
||||
|
||||
impl Backend {
|
||||
pub fn new() -> Result<Backend, Error> {
|
||||
let thread = moz_task::create_thread("osclientcerts").map_err(|nsresult| {
|
||||
error_here!(ErrorType::LibraryFailure, nsresult.error_name().to_string())
|
||||
})?;
|
||||
Ok(Backend {
|
||||
thread: thread
|
||||
.query_interface::<nsIEventTarget>()
|
||||
.ok_or(error_here!(ErrorType::LibraryFailure))?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ClientCertsBackend for Backend {
|
||||
type Cert = Cert;
|
||||
type Key = Key;
|
||||
|
||||
fn find_objects(&self) -> Result<(Vec<Cert>, Vec<Key>), Error> {
|
||||
let thread = self.thread.clone();
|
||||
let task = moz_task::spawn_onto("find_objects", &self.thread, async move {
|
||||
find_objects(&thread)
|
||||
});
|
||||
futures_executor::block_on(task)
|
||||
}
|
||||
}
|
||||
|
||||
fn find_objects(thread: &nsIEventTarget) -> Result<(Vec<Cert>, Vec<Key>), Error> {
|
||||
let mut certs = Vec::new();
|
||||
let mut keys = Vec::new();
|
||||
let identities = unsafe {
|
||||
@@ -821,7 +932,7 @@ impl ClientCertsBackend for Backend {
|
||||
for identity in identities.get_all_values().iter() {
|
||||
let identity = unsafe { SecIdentity::wrap_under_get_rule(*identity as SecIdentityRef) };
|
||||
let cert = Cert::new_from_identity(&identity);
|
||||
let key = Key::new(&identity);
|
||||
let key = Key::new(&identity, thread);
|
||||
if let (Ok(cert), Ok(key)) = (cert, key) {
|
||||
certs.push(cert);
|
||||
keys.push(key);
|
||||
@@ -837,5 +948,4 @@ impl ClientCertsBackend for Backend {
|
||||
}
|
||||
}
|
||||
Ok((certs, keys))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,8 @@ use winapi::shared::minwindef::{DWORD, PBYTE};
|
||||
use winapi::um::errhandlingapi::GetLastError;
|
||||
use winapi::um::ncrypt::*;
|
||||
use winapi::um::wincrypt::{HCRYPTHASH, HCRYPTPROV, *};
|
||||
use xpcom::interfaces::nsIEventTarget;
|
||||
use xpcom::{RefPtr, XpCom};
|
||||
|
||||
// winapi has some support for ncrypt.h, but not for this function.
|
||||
extern "system" {
|
||||
@@ -204,6 +206,12 @@ impl Deref for CertContext {
|
||||
}
|
||||
}
|
||||
|
||||
/// Safety: strictly speaking, it isn't safe to send `CertContext` across threads. The
|
||||
/// implementation handles this by wrapping `CertContext` in `ThreadSpecificHandles`. However, in
|
||||
/// order to implement `Drop` for `ThreadSpecificHandles`, the `CertContext` it holds must be sent
|
||||
/// to the appropriate thread, hence this impl.
|
||||
unsafe impl Send for CertContext {}
|
||||
|
||||
enum KeyHandle {
|
||||
NCrypt(NCRYPT_KEY_HANDLE),
|
||||
CryptoAPI(HCRYPTPROV, DWORD),
|
||||
@@ -271,6 +279,9 @@ impl Drop for KeyHandle {
|
||||
}
|
||||
}
|
||||
|
||||
/// Safety: see the comment for the `Send` impl for `CertContext`.
|
||||
unsafe impl Send for KeyHandle {}
|
||||
|
||||
fn sign_ncrypt(
|
||||
ncrypt_handle: &NCRYPT_KEY_HANDLE,
|
||||
data: &[u8],
|
||||
@@ -517,6 +528,112 @@ impl SignParams {
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper struct to hold onto OS-specific handles that must only be used on a particular thread.
|
||||
struct ThreadSpecificHandles {
|
||||
/// The only thread that these handles may be used on.
|
||||
thread: RefPtr<nsIEventTarget>,
|
||||
/// A handle on the OS mechanism that represents the certificate for a key.
|
||||
cert: Option<CertContext>,
|
||||
/// A handle on the OS mechanism that represents a key.
|
||||
key: Option<KeyHandle>,
|
||||
}
|
||||
|
||||
impl ThreadSpecificHandles {
|
||||
fn new(cert: CertContext, thread: &nsIEventTarget) -> ThreadSpecificHandles {
|
||||
ThreadSpecificHandles {
|
||||
thread: RefPtr::new(thread),
|
||||
cert: Some(cert),
|
||||
key: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn sign_or_get_signature_length(
|
||||
&mut self,
|
||||
key_type_enum: KeyType,
|
||||
data: &[u8],
|
||||
params: &Option<CK_RSA_PKCS_PSS_PARAMS>,
|
||||
do_signature: bool,
|
||||
) -> Result<Vec<u8>, Error> {
|
||||
let Some(cert) = self.cert.take() else {
|
||||
return Err(error_here!(ErrorType::LibraryFailure));
|
||||
};
|
||||
let mut maybe_key = self.key.take();
|
||||
let thread = self.thread.clone();
|
||||
let data = data.to_vec();
|
||||
let params = params.clone();
|
||||
let task = moz_task::spawn_onto("sign", &thread, async move {
|
||||
let result = sign_internal(
|
||||
&cert,
|
||||
&mut maybe_key,
|
||||
key_type_enum,
|
||||
&data,
|
||||
¶ms,
|
||||
do_signature,
|
||||
);
|
||||
if result.is_ok() {
|
||||
return (result, cert, maybe_key);
|
||||
}
|
||||
// Some devices appear to not work well when the key handle is held for too long or if a
|
||||
// card is inserted/removed while Firefox is running. Try refreshing the key handle.
|
||||
let _ = maybe_key.take();
|
||||
(
|
||||
sign_internal(
|
||||
&cert,
|
||||
&mut maybe_key,
|
||||
key_type_enum,
|
||||
&data,
|
||||
¶ms,
|
||||
do_signature,
|
||||
),
|
||||
cert,
|
||||
maybe_key,
|
||||
)
|
||||
});
|
||||
let (signature_result, cert, maybe_key) = futures_executor::block_on(task);
|
||||
self.cert = Some(cert);
|
||||
self.key = maybe_key;
|
||||
signature_result
|
||||
}
|
||||
}
|
||||
|
||||
/// data: the data to sign
|
||||
/// do_signature: if true, actually perform the signature. Otherwise, return a `Vec<u8>` of the
|
||||
/// length the signature would be, if performed.
|
||||
fn sign_internal(
|
||||
cert: &CertContext,
|
||||
maybe_key: &mut Option<KeyHandle>,
|
||||
key_type_enum: KeyType,
|
||||
data: &[u8],
|
||||
params: &Option<CK_RSA_PKCS_PSS_PARAMS>,
|
||||
do_signature: bool,
|
||||
) -> Result<Vec<u8>, Error> {
|
||||
// If this key hasn't been used for signing yet, there won't be a cached key handle. Obtain
|
||||
// and cache it if this is the case. Doing so can cause the underlying implementation to
|
||||
// show an authentication or pin prompt to the user. Caching the handle can avoid causing
|
||||
// multiple prompts to be displayed in some cases.
|
||||
if maybe_key.is_none() {
|
||||
let _ = maybe_key.replace(KeyHandle::from_cert(cert)?);
|
||||
}
|
||||
let Some(key) = maybe_key.as_ref() else {
|
||||
return Err(error_here!(ErrorType::LibraryFailure));
|
||||
};
|
||||
key.sign(data, params, do_signature, key_type_enum)
|
||||
}
|
||||
|
||||
impl Drop for ThreadSpecificHandles {
|
||||
fn drop(&mut self) {
|
||||
// Ensure any OS handles are dropped on the appropriate thread.
|
||||
let cert = self.cert.take();
|
||||
let key = self.key.take();
|
||||
let thread = self.thread.clone();
|
||||
let task = moz_task::spawn_onto("drop", &thread, async move {
|
||||
drop(cert);
|
||||
drop(key);
|
||||
});
|
||||
futures_executor::block_on(task)
|
||||
}
|
||||
}
|
||||
|
||||
/// A helper enum to identify a private key's type. We support EC and RSA.
|
||||
#[allow(clippy::upper_case_acronyms)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
@@ -527,8 +644,8 @@ pub enum KeyType {
|
||||
|
||||
/// Represents a private key for which there exists a corresponding certificate.
|
||||
pub struct Key {
|
||||
/// A handle on the OS mechanism that represents the certificate for this key.
|
||||
cert: CertContext,
|
||||
/// The OS handles for this key. May only be used on the thread they were created on.
|
||||
handles: ThreadSpecificHandles,
|
||||
/// PKCS #11 object class. Will be `CKO_PRIVATE_KEY`.
|
||||
class: Vec<u8>,
|
||||
/// Whether or not this is on a token. Will be `CK_TRUE`.
|
||||
@@ -546,12 +663,10 @@ pub struct Key {
|
||||
ec_params: Option<Vec<u8>>,
|
||||
/// An enum identifying this key's type.
|
||||
key_type_enum: KeyType,
|
||||
/// A handle on the OS mechanism that represents this key.
|
||||
key_handle: Option<KeyHandle>,
|
||||
}
|
||||
|
||||
impl Key {
|
||||
fn new(cert_context: PCCERT_CONTEXT) -> Result<Key, Error> {
|
||||
fn new(cert_context: PCCERT_CONTEXT, thread: &nsIEventTarget) -> Result<Key, Error> {
|
||||
let cert = unsafe { *cert_context };
|
||||
let cert_der =
|
||||
unsafe { slice::from_raw_parts(cert.pbCertEncoded, cert.cbCertEncoded as usize) };
|
||||
@@ -586,7 +701,7 @@ impl Key {
|
||||
};
|
||||
let cert = CertContext::new(cert_context);
|
||||
Ok(Key {
|
||||
cert,
|
||||
handles: ThreadSpecificHandles::new(cert, thread),
|
||||
class: serialize_uint(CKO_PRIVATE_KEY)?,
|
||||
token: serialize_uint(CK_TRUE)?,
|
||||
id,
|
||||
@@ -595,7 +710,6 @@ impl Key {
|
||||
modulus,
|
||||
ec_params,
|
||||
key_type_enum,
|
||||
key_handle: None,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -633,44 +747,14 @@ impl Key {
|
||||
}
|
||||
}
|
||||
|
||||
fn sign_with_retry(
|
||||
fn sign_or_get_signature_length(
|
||||
&mut self,
|
||||
data: &[u8],
|
||||
params: &Option<CK_RSA_PKCS_PSS_PARAMS>,
|
||||
do_signature: bool,
|
||||
) -> Result<Vec<u8>, Error> {
|
||||
let result = self.sign_internal(data, params, do_signature);
|
||||
if result.is_ok() {
|
||||
return result;
|
||||
}
|
||||
// Some devices appear to not work well when the key handle is held for too long or if a
|
||||
// card is inserted/removed while Firefox is running. Try refreshing the key handle.
|
||||
debug!("sign failed: refreshing key handle");
|
||||
let _ = self.key_handle.take();
|
||||
self.sign_internal(data, params, do_signature)
|
||||
}
|
||||
|
||||
/// data: the data to sign
|
||||
/// do_signature: if true, actually perform the signature. Otherwise, return a `Vec<u8>` of the
|
||||
/// length the signature would be, if performed.
|
||||
fn sign_internal(
|
||||
&mut self,
|
||||
data: &[u8],
|
||||
params: &Option<CK_RSA_PKCS_PSS_PARAMS>,
|
||||
do_signature: bool,
|
||||
) -> Result<Vec<u8>, Error> {
|
||||
// If this key hasn't been used for signing yet, there won't be a cached key handle. Obtain
|
||||
// and cache it if this is the case. Doing so can cause the underlying implementation to
|
||||
// show an authentication or pin prompt to the user. Caching the handle can avoid causing
|
||||
// multiple prompts to be displayed in some cases.
|
||||
if self.key_handle.is_none() {
|
||||
let _ = self.key_handle.replace(KeyHandle::from_cert(&self.cert)?);
|
||||
}
|
||||
let key = match &self.key_handle {
|
||||
Some(key) => key,
|
||||
None => return Err(error_here!(ErrorType::LibraryFailure)),
|
||||
};
|
||||
key.sign(data, params, do_signature, self.key_type_enum)
|
||||
self.handles
|
||||
.sign_or_get_signature_length(self.key_type_enum, data, params, do_signature)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -726,10 +810,8 @@ impl Sign for Key {
|
||||
data: &[u8],
|
||||
params: &Option<CK_RSA_PKCS_PSS_PARAMS>,
|
||||
) -> Result<usize, Error> {
|
||||
match self.sign_with_retry(data, params, false) {
|
||||
Ok(dummy_signature_bytes) => Ok(dummy_signature_bytes.len()),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
self.sign_or_get_signature_length(data, params, false)
|
||||
.map(|signature| signature.len())
|
||||
}
|
||||
|
||||
fn sign(
|
||||
@@ -737,7 +819,7 @@ impl Sign for Key {
|
||||
data: &[u8],
|
||||
params: &Option<CK_RSA_PKCS_PSS_PARAMS>,
|
||||
) -> Result<Vec<u8>, Error> {
|
||||
self.sign_with_retry(data, params, true)
|
||||
self.sign_or_get_signature_length(data, params, true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -809,20 +891,45 @@ fn gather_cert_contexts(cert_chain_context: *const CERT_CHAIN_CONTEXT) -> Vec<*c
|
||||
cert_contexts
|
||||
}
|
||||
|
||||
pub struct Backend {}
|
||||
pub struct Backend {
|
||||
/// A background thread that all OS API calls will be done on. This is to prevent issues with
|
||||
/// modules or implementations using thread-local state.
|
||||
thread: RefPtr<nsIEventTarget>,
|
||||
}
|
||||
|
||||
impl Backend {
|
||||
pub fn new() -> Result<Backend, Error> {
|
||||
let thread = moz_task::create_thread("osclientcerts").map_err(|nsresult| {
|
||||
error_here!(ErrorType::LibraryFailure, nsresult.error_name().to_string())
|
||||
})?;
|
||||
Ok(Backend {
|
||||
thread: thread
|
||||
.query_interface::<nsIEventTarget>()
|
||||
.ok_or(error_here!(ErrorType::LibraryFailure))?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ClientCertsBackend for Backend {
|
||||
type Cert = Cert;
|
||||
type Key = Key;
|
||||
|
||||
/// Attempts to enumerate certificates with private keys exposed by the OS. Currently only looks in
|
||||
/// the "My" cert store of the current user. In the future this may look in more locations.
|
||||
fn find_objects(&self) -> Result<(Vec<Cert>, Vec<Key>), Error> {
|
||||
let thread = self.thread.clone();
|
||||
let task = moz_task::spawn_onto("find_objects", &self.thread, async move {
|
||||
find_objects(&thread)
|
||||
});
|
||||
futures_executor::block_on(task)
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempts to enumerate certificates with private keys exposed by the OS. Currently only looks in
|
||||
/// the "My" cert store of the current user. In the future this may look in more locations.
|
||||
fn find_objects(thread: &nsIEventTarget) -> Result<(Vec<Cert>, Vec<Key>), Error> {
|
||||
let mut certs = Vec::new();
|
||||
let mut keys = Vec::new();
|
||||
let location_flags = CERT_SYSTEM_STORE_CURRENT_USER
|
||||
| CERT_STORE_OPEN_EXISTING_FLAG
|
||||
| CERT_STORE_READONLY_FLAG;
|
||||
let location_flags =
|
||||
CERT_SYSTEM_STORE_CURRENT_USER | CERT_STORE_OPEN_EXISTING_FLAG | CERT_STORE_READONLY_FLAG;
|
||||
let store_name = match CString::new("My") {
|
||||
Ok(store_name) => store_name,
|
||||
Err(_) => return Err(error_here!(ErrorType::LibraryFailure)),
|
||||
@@ -865,8 +972,7 @@ impl ClientCertsBackend for Backend {
|
||||
CERT_CHAIN_FIND_BY_ISSUER_CACHE_ONLY_FLAG
|
||||
| CERT_CHAIN_FIND_BY_ISSUER_CACHE_ONLY_URL_FLAG,
|
||||
CERT_CHAIN_FIND_BY_ISSUER,
|
||||
&find_params as *const CERT_CHAIN_FIND_ISSUER_PARA
|
||||
as *const winapi::ctypes::c_void,
|
||||
&find_params as *const CERT_CHAIN_FIND_ISSUER_PARA as *const winapi::ctypes::c_void,
|
||||
cert_chain_context,
|
||||
)
|
||||
};
|
||||
@@ -878,7 +984,7 @@ impl ClientCertsBackend for Backend {
|
||||
// after).
|
||||
match cert_contexts.get(0) {
|
||||
Some(cert_context) => {
|
||||
let key = match Key::new(*cert_context) {
|
||||
let key = match Key::new(*cert_context, thread) {
|
||||
Ok(key) => key,
|
||||
Err(_) => continue,
|
||||
};
|
||||
@@ -898,5 +1004,4 @@ impl ClientCertsBackend for Backend {
|
||||
}
|
||||
}
|
||||
Ok((certs, keys))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,9 +23,10 @@ extern crate rsclientcerts;
|
||||
extern crate sha2;
|
||||
#[cfg(all(target_os = "windows", not(target_arch = "aarch64")))]
|
||||
extern crate winapi;
|
||||
extern crate xpcom;
|
||||
|
||||
use pkcs11_bindings::*;
|
||||
use rsclientcerts::manager::ManagerProxy;
|
||||
use rsclientcerts::manager::Manager;
|
||||
use std::convert::TryInto;
|
||||
use std::sync::Mutex;
|
||||
use std::thread;
|
||||
@@ -44,24 +45,23 @@ use crate::backend_macos::Backend;
|
||||
#[cfg(all(target_os = "windows", not(target_arch = "aarch64")))]
|
||||
use crate::backend_windows::Backend;
|
||||
|
||||
/// The singleton `ManagerProxy` 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` forwards calls from any thread
|
||||
/// to a single thread where the real `Manager` does the actual work.
|
||||
static MANAGER_PROXY: Mutex<Option<ManagerProxy>> = Mutex::new(None);
|
||||
/// The singleton `Manager` 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. Note that the underlying OS
|
||||
/// APIs may not necessarily be thread safe. For platforms where this is the case, the `Backend`
|
||||
/// will synchronously run the relevant code on a background thread.
|
||||
static MANAGER: Mutex<Option<Manager<Backend>>> = 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 manager_proxy_guard = try_to_get_manager_proxy_guard!();
|
||||
// let manager = manager_proxy_guard_to_manager!(manager_proxy_guard);
|
||||
macro_rules! try_to_get_manager_proxy_guard {
|
||||
// let mut manager_guard = try_to_get_manager_guard!();
|
||||
// let manager = manager_guard_to_manager!(manager_guard);
|
||||
macro_rules! try_to_get_manager_guard {
|
||||
() => {
|
||||
match MANAGER_PROXY.lock() {
|
||||
Ok(maybe_manager_proxy) => maybe_manager_proxy,
|
||||
match MANAGER.lock() {
|
||||
Ok(maybe_manager) => maybe_manager,
|
||||
Err(poison_error) => {
|
||||
log_with_thread_id!(
|
||||
error,
|
||||
@@ -74,10 +74,10 @@ macro_rules! try_to_get_manager_proxy_guard {
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! manager_proxy_guard_to_manager {
|
||||
($manager_proxy_guard:ident) => {
|
||||
match $manager_proxy_guard.as_mut() {
|
||||
Some(manager_proxy) => manager_proxy,
|
||||
macro_rules! manager_guard_to_manager {
|
||||
($manager_guard:ident) => {
|
||||
match $manager_guard.as_mut() {
|
||||
Some(manager) => manager,
|
||||
None => {
|
||||
log_with_thread_id!(error, "module state expected to be set, but it is not");
|
||||
return CKR_DEVICE_ERROR;
|
||||
@@ -94,7 +94,7 @@ macro_rules! log_with_thread_id {
|
||||
}
|
||||
|
||||
/// This gets called to initialize the module. For this implementation, this consists of
|
||||
/// instantiating the `ManagerProxy`.
|
||||
/// instantiating the `Manager`.
|
||||
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.
|
||||
@@ -107,16 +107,16 @@ extern "C" fn C_Initialize(_pInitArgs: CK_VOID_PTR) -> CK_RV {
|
||||
);
|
||||
}
|
||||
|
||||
let mut manager_proxy_guard = try_to_get_manager_proxy_guard!();
|
||||
let manager_proxy = match ManagerProxy::new("osclientcerts", Backend {}) {
|
||||
Ok(p) => p,
|
||||
let backend = match Backend::new() {
|
||||
Ok(backend) => backend,
|
||||
Err(e) => {
|
||||
log_with_thread_id!(error, "C_Initialize: ManagerProxy: {}", e);
|
||||
log_with_thread_id!(error, "C_Initialize: Backend::new() failed: {}", e);
|
||||
return CKR_DEVICE_ERROR;
|
||||
}
|
||||
};
|
||||
match manager_proxy_guard.replace(manager_proxy) {
|
||||
Some(_unexpected_previous_manager_proxy) => {
|
||||
let mut manager_guard = try_to_get_manager_guard!();
|
||||
match manager_guard.replace(Manager::new(backend)) {
|
||||
Some(_unexpected_previous_manager) => {
|
||||
log_with_thread_id!(
|
||||
warn,
|
||||
"C_Initialize: replacing previously set module state (this is expected on macOS but not on Windows)"
|
||||
@@ -129,16 +129,15 @@ extern "C" fn C_Initialize(_pInitArgs: CK_VOID_PTR) -> CK_RV {
|
||||
}
|
||||
|
||||
extern "C" fn C_Finalize(_pReserved: CK_VOID_PTR) -> CK_RV {
|
||||
let mut manager_proxy_guard = try_to_get_manager_proxy_guard!();
|
||||
let manager = manager_proxy_guard_to_manager!(manager_proxy_guard);
|
||||
match manager.stop() {
|
||||
Ok(()) => {
|
||||
let mut manager_guard = try_to_get_manager_guard!();
|
||||
match manager_guard.take() {
|
||||
Some(_) => {
|
||||
log_with_thread_id!(debug, "C_Finalize: CKR_OK");
|
||||
CKR_OK
|
||||
}
|
||||
Err(e) => {
|
||||
log_with_thread_id!(error, "C_Finalize: CKR_DEVICE_ERROR: {}", e);
|
||||
CKR_DEVICE_ERROR
|
||||
None => {
|
||||
log_with_thread_id!(debug, "C_Finalize: CKR_CRYPTOKI_NOT_INITIALIZED");
|
||||
CKR_CRYPTOKI_NOT_INITIALIZED
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -315,8 +314,7 @@ extern "C" fn C_SetPIN(
|
||||
CKR_FUNCTION_NOT_SUPPORTED
|
||||
}
|
||||
|
||||
/// This gets called to create a new session. This module defers to the `ManagerProxy` to implement
|
||||
/// this.
|
||||
/// This gets called to create a new session. This module defers to the `Manager` to implement this.
|
||||
extern "C" fn C_OpenSession(
|
||||
slotID: CK_SLOT_ID,
|
||||
_flags: CK_FLAGS,
|
||||
@@ -328,8 +326,8 @@ extern "C" fn C_OpenSession(
|
||||
log_with_thread_id!(error, "C_OpenSession: CKR_ARGUMENTS_BAD");
|
||||
return CKR_ARGUMENTS_BAD;
|
||||
}
|
||||
let mut manager_proxy_guard = try_to_get_manager_proxy_guard!();
|
||||
let manager = manager_proxy_guard_to_manager!(manager_proxy_guard);
|
||||
let mut manager_guard = try_to_get_manager_guard!();
|
||||
let manager = manager_guard_to_manager!(manager_guard);
|
||||
let session_handle = match manager.open_session() {
|
||||
Ok(session_handle) => session_handle,
|
||||
Err(e) => {
|
||||
@@ -344,10 +342,10 @@ extern "C" fn C_OpenSession(
|
||||
CKR_OK
|
||||
}
|
||||
|
||||
/// This gets called to close a session. This is handled by the `ManagerProxy`.
|
||||
/// This gets called to close a session. This is handled by the `Manager`.
|
||||
extern "C" fn C_CloseSession(hSession: CK_SESSION_HANDLE) -> CK_RV {
|
||||
let mut manager_proxy_guard = try_to_get_manager_proxy_guard!();
|
||||
let manager = manager_proxy_guard_to_manager!(manager_proxy_guard);
|
||||
let mut manager_guard = try_to_get_manager_guard!();
|
||||
let manager = manager_guard_to_manager!(manager_guard);
|
||||
if manager.close_session(hSession).is_err() {
|
||||
log_with_thread_id!(error, "C_CloseSession: CKR_SESSION_HANDLE_INVALID");
|
||||
return CKR_SESSION_HANDLE_INVALID;
|
||||
@@ -356,14 +354,14 @@ extern "C" fn C_CloseSession(hSession: CK_SESSION_HANDLE) -> CK_RV {
|
||||
CKR_OK
|
||||
}
|
||||
|
||||
/// This gets called to close all open sessions at once. This is handled by the `ManagerProxy`.
|
||||
/// This gets called to close all open sessions at once. This is handled by the `Manager`.
|
||||
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;
|
||||
}
|
||||
let mut manager_proxy_guard = try_to_get_manager_proxy_guard!();
|
||||
let manager = manager_proxy_guard_to_manager!(manager_proxy_guard);
|
||||
let mut manager_guard = try_to_get_manager_guard!();
|
||||
let manager = manager_guard_to_manager!(manager_guard);
|
||||
match manager.close_all_sessions() {
|
||||
Ok(()) => {
|
||||
log_with_thread_id!(debug, "C_CloseAllSessions: CKR_OK");
|
||||
@@ -459,7 +457,7 @@ extern "C" fn C_GetObjectSize(
|
||||
}
|
||||
|
||||
/// 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
|
||||
/// given handle. This module implements this by requesting that the `Manager` 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
|
||||
@@ -479,8 +477,8 @@ extern "C" fn C_GetAttributeValue(
|
||||
let attr = unsafe { &*pTemplate.add(i) };
|
||||
attr_types.push(attr.type_);
|
||||
}
|
||||
let mut manager_proxy_guard = try_to_get_manager_proxy_guard!();
|
||||
let manager = manager_proxy_guard_to_manager!(manager_proxy_guard);
|
||||
let mut manager_guard = try_to_get_manager_guard!();
|
||||
let manager = manager_guard_to_manager!(manager_guard);
|
||||
let values = match manager.get_attributes(hObject, attr_types) {
|
||||
Ok(values) => values,
|
||||
Err(e) => {
|
||||
@@ -572,8 +570,8 @@ const RELEVANT_ATTRIBUTES: &[CK_ATTRIBUTE_TYPE] = &[
|
||||
];
|
||||
|
||||
/// 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.
|
||||
/// module implements this by gathering the attributes and passing them to the `Manager` to start
|
||||
/// the search.
|
||||
extern "C" fn C_FindObjectsInit(
|
||||
hSession: CK_SESSION_HANDLE,
|
||||
pTemplate: CK_ATTRIBUTE_PTR,
|
||||
@@ -602,8 +600,8 @@ extern "C" fn C_FindObjectsInit(
|
||||
};
|
||||
attrs.push((attr_type, slice.to_owned()));
|
||||
}
|
||||
let mut manager_proxy_guard = try_to_get_manager_proxy_guard!();
|
||||
let manager = manager_proxy_guard_to_manager!(manager_proxy_guard);
|
||||
let mut manager_guard = try_to_get_manager_guard!();
|
||||
let manager = manager_guard_to_manager!(manager_guard);
|
||||
match manager.start_search(hSession, attrs) {
|
||||
Ok(()) => {}
|
||||
Err(e) => {
|
||||
@@ -616,7 +614,7 @@ extern "C" fn C_FindObjectsInit(
|
||||
}
|
||||
|
||||
/// 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
|
||||
/// implements this by looking up the search in the `Manager` and copying out the matching
|
||||
/// object handles.
|
||||
extern "C" fn C_FindObjects(
|
||||
hSession: CK_SESSION_HANDLE,
|
||||
@@ -628,8 +626,8 @@ extern "C" fn C_FindObjects(
|
||||
log_with_thread_id!(error, "C_FindObjects: CKR_ARGUMENTS_BAD");
|
||||
return CKR_ARGUMENTS_BAD;
|
||||
}
|
||||
let mut manager_proxy_guard = try_to_get_manager_proxy_guard!();
|
||||
let manager = manager_proxy_guard_to_manager!(manager_proxy_guard);
|
||||
let mut manager_guard = try_to_get_manager_guard!();
|
||||
let manager = manager_guard_to_manager!(manager_guard);
|
||||
let handles = match manager.search(hSession, ulMaxObjectCount as usize) {
|
||||
Ok(handles) => handles,
|
||||
Err(e) => {
|
||||
@@ -657,10 +655,10 @@ extern "C" fn C_FindObjects(
|
||||
}
|
||||
|
||||
/// This gets called after `C_FindObjectsInit` and `C_FindObjects` to finish a search. The module
|
||||
/// tells the `ManagerProxy` to clear the search.
|
||||
/// tells the `Manager` to clear the search.
|
||||
extern "C" fn C_FindObjectsFinal(hSession: CK_SESSION_HANDLE) -> CK_RV {
|
||||
let mut manager_proxy_guard = try_to_get_manager_proxy_guard!();
|
||||
let manager = manager_proxy_guard_to_manager!(manager_proxy_guard);
|
||||
let mut manager_guard = try_to_get_manager_guard!();
|
||||
let manager = manager_guard_to_manager!(manager_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(()) => {
|
||||
@@ -793,8 +791,7 @@ extern "C" fn C_DigestFinal(
|
||||
CKR_FUNCTION_NOT_SUPPORTED
|
||||
}
|
||||
|
||||
/// This gets called to set up a sign operation. The module essentially defers to the
|
||||
/// `ManagerProxy`.
|
||||
/// This gets called to set up a sign operation. The module essentially defers to the `Manager`.
|
||||
extern "C" fn C_SignInit(
|
||||
hSession: CK_SESSION_HANDLE,
|
||||
pMechanism: CK_MECHANISM_PTR,
|
||||
@@ -821,8 +818,8 @@ extern "C" fn C_SignInit(
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let mut manager_proxy_guard = try_to_get_manager_proxy_guard!();
|
||||
let manager = manager_proxy_guard_to_manager!(manager_proxy_guard);
|
||||
let mut manager_guard = try_to_get_manager_guard!();
|
||||
let manager = manager_guard_to_manager!(manager_guard);
|
||||
match manager.start_sign(hSession, hKey, mechanism_params) {
|
||||
Ok(()) => {}
|
||||
Err(e) => {
|
||||
@@ -836,7 +833,7 @@ extern "C" fn C_SignInit(
|
||||
|
||||
/// NSS calls this after `C_SignInit` (there are more ways in the PKCS #11 specification to sign
|
||||
/// data, but this is the only way supported by this module). The module essentially defers to the
|
||||
/// `ManagerProxy` and copies out the resulting signature.
|
||||
/// `Manager` and copies out the resulting signature.
|
||||
extern "C" fn C_Sign(
|
||||
hSession: CK_SESSION_HANDLE,
|
||||
pData: CK_BYTE_PTR,
|
||||
@@ -850,8 +847,8 @@ extern "C" fn C_Sign(
|
||||
}
|
||||
let data = unsafe { std::slice::from_raw_parts(pData, ulDataLen as usize) };
|
||||
if pSignature.is_null() {
|
||||
let mut manager_proxy_guard = try_to_get_manager_proxy_guard!();
|
||||
let manager = manager_proxy_guard_to_manager!(manager_proxy_guard);
|
||||
let mut manager_guard = try_to_get_manager_guard!();
|
||||
let manager = manager_guard_to_manager!(manager_guard);
|
||||
match manager.get_signature_length(hSession, data.to_vec()) {
|
||||
Ok(signature_length) => unsafe {
|
||||
*pulSignatureLen = signature_length as CK_ULONG;
|
||||
@@ -862,8 +859,8 @@ extern "C" fn C_Sign(
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let mut manager_proxy_guard = try_to_get_manager_proxy_guard!();
|
||||
let manager = manager_proxy_guard_to_manager!(manager_proxy_guard);
|
||||
let mut manager_guard = try_to_get_manager_guard!();
|
||||
let manager = manager_guard_to_manager!(manager_guard);
|
||||
match manager.sign(hSession, data.to_vec()) {
|
||||
Ok(signature) => {
|
||||
let signature_capacity = unsafe { *pulSignatureLen } as usize;
|
||||
|
||||
@@ -5,9 +5,6 @@
|
||||
|
||||
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};
|
||||
@@ -43,281 +40,6 @@ pub trait ClientCertsBackend {
|
||||
fn find_objects(&self) -> Result<(Vec<Self::Cert>, Vec<Self::Key>), Error>;
|
||||
}
|
||||
|
||||
/// 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(),
|
||||
CloseSession(CK_SESSION_HANDLE),
|
||||
CloseAllSessions(),
|
||||
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`.
|
||||
pub struct ManagerProxy {
|
||||
sender: ManagerArgumentsSender,
|
||||
receiver: ManagerReturnValueReceiver,
|
||||
thread_handle: Option<JoinHandle<()>>,
|
||||
}
|
||||
|
||||
impl ManagerProxy {
|
||||
pub fn new<B: ClientCertsBackend + Send + 'static>(
|
||||
name: &'static str,
|
||||
backend: B,
|
||||
) -> Result<ManagerProxy, Error> {
|
||||
let (proxy_sender, manager_receiver) = channel();
|
||||
let (manager_sender, proxy_receiver) = channel();
|
||||
let thread_handle = thread::Builder::new().name(name.into()).spawn(move || {
|
||||
#[cfg(not(test))]
|
||||
gecko_profiler::register_thread(name);
|
||||
|
||||
let mut real_manager = Manager::new(backend);
|
||||
while let Ok(arguments) = manager_receiver.recv() {
|
||||
let results = match arguments {
|
||||
ManagerArguments::OpenSession() => {
|
||||
ManagerReturnValue::OpenSession(real_manager.open_session())
|
||||
}
|
||||
ManagerArguments::CloseSession(session_handle) => {
|
||||
ManagerReturnValue::CloseSession(real_manager.close_session(session_handle))
|
||||
}
|
||||
ManagerArguments::CloseAllSessions() => {
|
||||
ManagerReturnValue::CloseAllSessions(
|
||||
real_manager.close_all_sessions(),
|
||||
)
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(test))]
|
||||
gecko_profiler::unregister_thread();
|
||||
});
|
||||
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<ManagerReturnValue, Error> {
|
||||
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) -> Result<CK_SESSION_HANDLE, Error> {
|
||||
manager_proxy_fn_impl!(
|
||||
self,
|
||||
ManagerArguments::OpenSession(),
|
||||
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) -> Result<(), Error> {
|
||||
manager_proxy_fn_impl!(
|
||||
self,
|
||||
ManagerArguments::CloseAllSessions(),
|
||||
ManagerReturnValue::CloseAllSessions
|
||||
)
|
||||
}
|
||||
|
||||
pub fn start_search(
|
||||
&mut self,
|
||||
session: CK_SESSION_HANDLE,
|
||||
attrs: Vec<(CK_ATTRIBUTE_TYPE, Vec<u8>)>,
|
||||
) -> 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<Vec<CK_OBJECT_HANDLE>, 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<CK_ATTRIBUTE_TYPE>,
|
||||
) -> Result<Vec<Option<Vec<u8>>>, 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<CK_RSA_PKCS_PSS_PARAMS>,
|
||||
) -> 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<u8>,
|
||||
) -> Result<usize, Error> {
|
||||
manager_proxy_fn_impl!(
|
||||
self,
|
||||
ManagerArguments::GetSignatureLength(session, data),
|
||||
ManagerReturnValue::GetSignatureLength
|
||||
)
|
||||
}
|
||||
|
||||
pub fn sign(&mut self, session: CK_SESSION_HANDLE, data: Vec<u8>) -> Result<Vec<u8>, 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))
|
||||
}
|
||||
}
|
||||
|
||||
const SUPPORTED_ATTRIBUTES: &[CK_ATTRIBUTE_TYPE] = &[
|
||||
CKA_CLASS,
|
||||
CKA_TOKEN,
|
||||
|
||||
Reference in New Issue
Block a user