Backed out changeset f326ce493602 (bug 1962280) for causing build bustages. CLOSED TREE

This commit is contained in:
smolnar
2025-04-30 02:27:52 +03:00
parent d0b0bd1a49
commit 59dc128e37
9 changed files with 588 additions and 529 deletions

4
Cargo.lock generated
View File

@@ -4972,16 +4972,14 @@ 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]]

View File

@@ -102,6 +102,11 @@ 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);

View File

@@ -1618,15 +1618,8 @@ 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);
MutexAutoLock lock(mMutex);
mDefaultCertVerifier = nullptr;
// 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).

View File

@@ -9,14 +9,13 @@ 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"
xpcom = { path = "../../../../xpcom/rust/xpcom" }
static_prefs = { path = "../../../../modules/libpref/init/static_prefs"}
[target."cfg(any(target_os = \"macos\", target_os = \"ios\"))".dependencies.core-foundation]
version = "0.9"

View File

@@ -7,7 +7,7 @@ use pkcs11_bindings::*;
use rsclientcerts::error::{Error, ErrorType};
use rsclientcerts::manager::{ClientCertsBackend, CryptokiObject, Sign};
use rsclientcerts::util::*;
use sha2::{Digest, Sha256};
use sha2::{Sha256, Digest};
use std::ffi::{c_char, c_void, CString};
type FindObjectsCallback = Option<
@@ -184,7 +184,11 @@ 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.
@@ -409,12 +413,6 @@ 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;

View File

@@ -22,8 +22,6 @@ 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,
@@ -36,12 +34,6 @@ 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;
@@ -54,9 +46,6 @@ 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;
@@ -503,141 +492,8 @@ 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, &params);
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, &params);
// 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, &params) {
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, mut 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 {
handles: ThreadSpecificHandles,
identity: SecIdentity,
class: Vec<u8>,
token: Vec<u8>,
id: Vec<u8>,
@@ -646,10 +502,11 @@ 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, thread: &nsIEventTarget) -> Result<Key, Error> {
fn new(identity: &SecIdentity) -> 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();
@@ -689,7 +546,7 @@ impl Key {
};
Ok(Key {
handles: ThreadSpecificHandles::new(identity.clone(), thread),
identity: identity.clone(),
class: serialize_uint(CKO_PRIVATE_KEY)?,
token: serialize_uint(CK_TRUE)?,
id,
@@ -698,6 +555,7 @@ impl Key {
modulus,
ec_params,
key_type_enum,
key_handle: None,
})
}
@@ -734,6 +592,41 @@ 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 {
@@ -800,8 +693,30 @@ impl Sign for Key {
data: &[u8],
params: &Option<CK_RSA_PKCS_PSS_PARAMS>,
) -> Result<Vec<u8>, Error> {
self.handles
.sign(self.key_type_enum, self.modulus.clone(), data, params)
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)
}
}
@@ -868,84 +783,59 @@ fn get_issuers(identity: &SecIdentity) -> Result<Vec<SecCertificate>, Error> {
Ok(certificates)
}
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))?,
})
}
}
pub struct Backend {}
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 {
let class_key = CFString::wrap_under_get_rule(kSecClass);
let class_value = CFString::wrap_under_get_rule(kSecClassIdentity);
let return_ref_key = CFString::wrap_under_get_rule(kSecReturnRef);
let return_ref_value = CFBoolean::wrap_under_get_rule(kCFBooleanTrue);
let match_key = CFString::wrap_under_get_rule(kSecMatchLimit);
let match_value = CFString::wrap_under_get_rule(kSecMatchLimitAll);
let vals = vec![
(class_key.as_CFType(), class_value.as_CFType()),
(return_ref_key.as_CFType(), return_ref_value.as_CFType()),
(match_key.as_CFType(), match_value.as_CFType()),
];
let dict = CFDictionary::from_CFType_pairs(&vals);
let mut result = std::ptr::null();
let status = SecItemCopyMatching(dict.as_CFTypeRef() as CFDictionaryRef, &mut result);
if status == errSecItemNotFound {
return Ok((certs, keys));
}
if status != errSecSuccess {
return Err(error_here!(ErrorType::ExternalError, status.to_string()));
}
if result.is_null() {
return Err(error_here!(ErrorType::ExternalError));
}
CFArray::<SecIdentityRef>::wrap_under_create_rule(result as CFArrayRef)
};
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, thread);
if let (Ok(cert), Ok(key)) = (cert, key) {
certs.push(cert);
keys.push(key);
} else {
continue;
}
if let Ok(issuers) = get_issuers(&identity) {
for issuer in issuers {
if let Ok(cert) = Cert::new_from_certificate(&issuer) {
certs.push(cert);
let mut certs = Vec::new();
let mut keys = Vec::new();
let identities = unsafe {
let class_key = CFString::wrap_under_get_rule(kSecClass);
let class_value = CFString::wrap_under_get_rule(kSecClassIdentity);
let return_ref_key = CFString::wrap_under_get_rule(kSecReturnRef);
let return_ref_value = CFBoolean::wrap_under_get_rule(kCFBooleanTrue);
let match_key = CFString::wrap_under_get_rule(kSecMatchLimit);
let match_value = CFString::wrap_under_get_rule(kSecMatchLimitAll);
let vals = vec![
(class_key.as_CFType(), class_value.as_CFType()),
(return_ref_key.as_CFType(), return_ref_value.as_CFType()),
(match_key.as_CFType(), match_value.as_CFType()),
];
let dict = CFDictionary::from_CFType_pairs(&vals);
let mut result = std::ptr::null();
let status = SecItemCopyMatching(dict.as_CFTypeRef() as CFDictionaryRef, &mut result);
if status == errSecItemNotFound {
return Ok((certs, keys));
}
if status != errSecSuccess {
return Err(error_here!(ErrorType::ExternalError, status.to_string()));
}
if result.is_null() {
return Err(error_here!(ErrorType::ExternalError));
}
CFArray::<SecIdentityRef>::wrap_under_create_rule(result as CFArrayRef)
};
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);
if let (Ok(cert), Ok(key)) = (cert, key) {
certs.push(cert);
keys.push(key);
} else {
continue;
}
if let Ok(issuers) = get_issuers(&identity) {
for issuer in issuers {
if let Ok(cert) = Cert::new_from_certificate(&issuer) {
certs.push(cert);
}
}
}
}
Ok((certs, keys))
}
Ok((certs, keys))
}

View File

@@ -19,8 +19,6 @@ 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" {
@@ -206,12 +204,6 @@ 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),
@@ -279,9 +271,6 @@ 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],
@@ -528,112 +517,6 @@ 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,
&params,
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,
&params,
do_signature,
),
cert,
maybe_key,
)
});
let (signature_result, cert, mut 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)]
@@ -644,8 +527,8 @@ pub enum KeyType {
/// Represents a private key for which there exists a corresponding certificate.
pub struct Key {
/// The OS handles for this key. May only be used on the thread they were created on.
handles: ThreadSpecificHandles,
/// A handle on the OS mechanism that represents the certificate for this key.
cert: CertContext,
/// PKCS #11 object class. Will be `CKO_PRIVATE_KEY`.
class: Vec<u8>,
/// Whether or not this is on a token. Will be `CK_TRUE`.
@@ -663,10 +546,12 @@ 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, thread: &nsIEventTarget) -> Result<Key, Error> {
fn new(cert_context: PCCERT_CONTEXT) -> Result<Key, Error> {
let cert = unsafe { *cert_context };
let cert_der =
unsafe { slice::from_raw_parts(cert.pbCertEncoded, cert.cbCertEncoded as usize) };
@@ -701,7 +586,7 @@ impl Key {
};
let cert = CertContext::new(cert_context);
Ok(Key {
handles: ThreadSpecificHandles::new(cert, thread),
cert,
class: serialize_uint(CKO_PRIVATE_KEY)?,
token: serialize_uint(CK_TRUE)?,
id,
@@ -710,6 +595,7 @@ impl Key {
modulus,
ec_params,
key_type_enum,
key_handle: None,
})
}
@@ -747,14 +633,44 @@ impl Key {
}
}
fn sign_or_get_signature_length(
fn sign_with_retry(
&mut self,
data: &[u8],
params: &Option<CK_RSA_PKCS_PSS_PARAMS>,
do_signature: bool,
) -> Result<Vec<u8>, Error> {
self.handles
.sign_or_get_signature_length(self.key_type_enum, data, params, do_signature)
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)
}
}
@@ -810,8 +726,10 @@ impl Sign for Key {
data: &[u8],
params: &Option<CK_RSA_PKCS_PSS_PARAMS>,
) -> Result<usize, Error> {
self.sign_or_get_signature_length(data, params, false)
.map(|signature| signature.len())
match self.sign_with_retry(data, params, false) {
Ok(dummy_signature_bytes) => Ok(dummy_signature_bytes.len()),
Err(e) => Err(e),
}
}
fn sign(
@@ -819,7 +737,7 @@ impl Sign for Key {
data: &[u8],
params: &Option<CK_RSA_PKCS_PSS_PARAMS>,
) -> Result<Vec<u8>, Error> {
self.sign_or_get_signature_length(data, params, true)
self.sign_with_retry(data, params, true)
}
}
@@ -891,117 +809,94 @@ fn gather_cert_contexts(cert_chain_context: *const CERT_CHAIN_CONTEXT) -> Vec<*c
cert_contexts
}
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))?,
})
}
}
pub struct Backend {}
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 store_name = match CString::new("My") {
Ok(store_name) => store_name,
Err(_) => return Err(error_here!(ErrorType::LibraryFailure)),
};
let store = CertStore::new(unsafe {
CertOpenStore(
CERT_STORE_PROV_SYSTEM_REGISTRY_A,
0,
0,
location_flags,
store_name.as_ptr() as *const winapi::ctypes::c_void,
)
});
if store.is_null() {
return Err(error_here!(ErrorType::ExternalError));
}
let find_params = CERT_CHAIN_FIND_ISSUER_PARA {
cbSize: std::mem::size_of::<CERT_CHAIN_FIND_ISSUER_PARA>() as u32,
pszUsageIdentifier: std::ptr::null(),
dwKeySpec: 0,
dwAcquirePrivateKeyFlags: 0,
cIssuer: 0,
rgIssuer: std::ptr::null_mut(),
pfnFindCallback: None,
pvFindArg: std::ptr::null_mut(),
pdwIssuerChainIndex: std::ptr::null_mut(),
pdwIssuerElementIndex: std::ptr::null_mut(),
};
let mut cert_chain_context: PCCERT_CHAIN_CONTEXT = std::ptr::null_mut();
loop {
// CertFindChainInStore finds all certificates with private keys in the store. It also
// attempts to build a verified certificate chain to a trust anchor for each certificate.
// We gather and hold onto these extra certificates so that gecko can use them when
// filtering potential client certificates according to the acceptable CAs list sent by
// servers when they request client certificates.
cert_chain_context = unsafe {
CertFindChainInStore(
*store,
X509_ASN_ENCODING,
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,
cert_chain_context,
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 store_name = match CString::new("My") {
Ok(store_name) => store_name,
Err(_) => return Err(error_here!(ErrorType::LibraryFailure)),
};
let store = CertStore::new(unsafe {
CertOpenStore(
CERT_STORE_PROV_SYSTEM_REGISTRY_A,
0,
0,
location_flags,
store_name.as_ptr() as *const winapi::ctypes::c_void,
)
};
if cert_chain_context.is_null() {
break;
});
if store.is_null() {
return Err(error_here!(ErrorType::ExternalError));
}
let cert_contexts = gather_cert_contexts(cert_chain_context);
// The 0th CERT_CONTEXT is the end-entity (i.e. the certificate with the private key we're
// after).
match cert_contexts.get(0) {
Some(cert_context) => {
let key = match Key::new(*cert_context, thread) {
Ok(key) => key,
Err(_) => continue,
};
let cert = match Cert::new(*cert_context) {
Ok(cert) => cert,
Err(_) => continue,
};
certs.push(cert);
keys.push(key);
}
None => {}
let find_params = CERT_CHAIN_FIND_ISSUER_PARA {
cbSize: std::mem::size_of::<CERT_CHAIN_FIND_ISSUER_PARA>() as u32,
pszUsageIdentifier: std::ptr::null(),
dwKeySpec: 0,
dwAcquirePrivateKeyFlags: 0,
cIssuer: 0,
rgIssuer: std::ptr::null_mut(),
pfnFindCallback: None,
pvFindArg: std::ptr::null_mut(),
pdwIssuerChainIndex: std::ptr::null_mut(),
pdwIssuerElementIndex: std::ptr::null_mut(),
};
for cert_context in cert_contexts.iter().skip(1) {
if let Ok(cert) = Cert::new(*cert_context) {
certs.push(cert);
let mut cert_chain_context: PCCERT_CHAIN_CONTEXT = std::ptr::null_mut();
loop {
// CertFindChainInStore finds all certificates with private keys in the store. It also
// attempts to build a verified certificate chain to a trust anchor for each certificate.
// We gather and hold onto these extra certificates so that gecko can use them when
// filtering potential client certificates according to the acceptable CAs list sent by
// servers when they request client certificates.
cert_chain_context = unsafe {
CertFindChainInStore(
*store,
X509_ASN_ENCODING,
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,
cert_chain_context,
)
};
if cert_chain_context.is_null() {
break;
}
let cert_contexts = gather_cert_contexts(cert_chain_context);
// The 0th CERT_CONTEXT is the end-entity (i.e. the certificate with the private key we're
// after).
match cert_contexts.get(0) {
Some(cert_context) => {
let key = match Key::new(*cert_context) {
Ok(key) => key,
Err(_) => continue,
};
let cert = match Cert::new(*cert_context) {
Ok(cert) => cert,
Err(_) => continue,
};
certs.push(cert);
keys.push(key);
}
None => {}
};
for cert_context in cert_contexts.iter().skip(1) {
if let Ok(cert) = Cert::new(*cert_context) {
certs.push(cert);
}
}
}
Ok((certs, keys))
}
Ok((certs, keys))
}

View File

@@ -23,10 +23,9 @@ 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::Manager;
use rsclientcerts::manager::ManagerProxy;
use std::convert::TryInto;
use std::sync::Mutex;
use std::thread;
@@ -45,23 +44,24 @@ use crate::backend_macos::Backend;
#[cfg(all(target_os = "windows", not(target_arch = "aarch64")))]
use crate::backend_windows::Backend;
/// 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);
/// 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);
// 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_guard = try_to_get_manager_guard!();
// let manager = manager_guard_to_manager!(manager_guard);
macro_rules! try_to_get_manager_guard {
// 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 {
() => {
match MANAGER.lock() {
Ok(maybe_manager) => maybe_manager,
match MANAGER_PROXY.lock() {
Ok(maybe_manager_proxy) => maybe_manager_proxy,
Err(poison_error) => {
log_with_thread_id!(
error,
@@ -74,10 +74,10 @@ macro_rules! try_to_get_manager_guard {
};
}
macro_rules! manager_guard_to_manager {
($manager_guard:ident) => {
match $manager_guard.as_mut() {
Some(manager) => manager,
macro_rules! manager_proxy_guard_to_manager {
($manager_proxy_guard:ident) => {
match $manager_proxy_guard.as_mut() {
Some(manager_proxy) => manager_proxy,
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 `Manager`.
/// 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.
@@ -107,16 +107,16 @@ extern "C" fn C_Initialize(_pInitArgs: CK_VOID_PTR) -> CK_RV {
);
}
let backend = match Backend::new() {
Ok(backend) => backend,
let mut manager_proxy_guard = try_to_get_manager_proxy_guard!();
let manager_proxy = match ManagerProxy::new("osclientcerts", Backend {}) {
Ok(p) => p,
Err(e) => {
log_with_thread_id!(error, "C_Initialize: Backend::new() failed: {}", e);
log_with_thread_id!(error, "C_Initialize: ManagerProxy: {}", e);
return CKR_DEVICE_ERROR;
}
};
let mut manager_guard = try_to_get_manager_guard!();
match manager_guard.replace(Manager::new(backend)) {
Some(_unexpected_previous_manager) => {
match manager_proxy_guard.replace(manager_proxy) {
Some(_unexpected_previous_manager_proxy) => {
log_with_thread_id!(
warn,
"C_Initialize: replacing previously set module state (this is expected on macOS but not on Windows)"
@@ -129,15 +129,16 @@ 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_guard = try_to_get_manager_guard!();
match manager_guard.take() {
Some(_) => {
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(()) => {
log_with_thread_id!(debug, "C_Finalize: CKR_OK");
CKR_OK
}
None => {
log_with_thread_id!(debug, "C_Finalize: CKR_CRYPTOKI_NOT_INITIALIZED");
CKR_CRYPTOKI_NOT_INITIALIZED
Err(e) => {
log_with_thread_id!(error, "C_Finalize: CKR_DEVICE_ERROR: {}", e);
CKR_DEVICE_ERROR
}
}
}
@@ -314,7 +315,8 @@ extern "C" fn C_SetPIN(
CKR_FUNCTION_NOT_SUPPORTED
}
/// This gets called to create a new session. This module defers to the `Manager` to implement this.
/// 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,
@@ -326,8 +328,8 @@ extern "C" fn C_OpenSession(
log_with_thread_id!(error, "C_OpenSession: CKR_ARGUMENTS_BAD");
return CKR_ARGUMENTS_BAD;
}
let mut manager_guard = try_to_get_manager_guard!();
let manager = manager_guard_to_manager!(manager_guard);
let mut manager_proxy_guard = try_to_get_manager_proxy_guard!();
let manager = manager_proxy_guard_to_manager!(manager_proxy_guard);
let session_handle = match manager.open_session() {
Ok(session_handle) => session_handle,
Err(e) => {
@@ -342,10 +344,10 @@ extern "C" fn C_OpenSession(
CKR_OK
}
/// This gets called to close a session. This is handled by the `Manager`.
/// This gets called to close a session. This is handled by the `ManagerProxy`.
extern "C" fn C_CloseSession(hSession: CK_SESSION_HANDLE) -> CK_RV {
let mut manager_guard = try_to_get_manager_guard!();
let manager = manager_guard_to_manager!(manager_guard);
let mut manager_proxy_guard = try_to_get_manager_proxy_guard!();
let manager = manager_proxy_guard_to_manager!(manager_proxy_guard);
if manager.close_session(hSession).is_err() {
log_with_thread_id!(error, "C_CloseSession: CKR_SESSION_HANDLE_INVALID");
return CKR_SESSION_HANDLE_INVALID;
@@ -354,14 +356,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 `Manager`.
/// 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;
}
let mut manager_guard = try_to_get_manager_guard!();
let manager = manager_guard_to_manager!(manager_guard);
let mut manager_proxy_guard = try_to_get_manager_proxy_guard!();
let manager = manager_proxy_guard_to_manager!(manager_proxy_guard);
match manager.close_all_sessions() {
Ok(()) => {
log_with_thread_id!(debug, "C_CloseAllSessions: CKR_OK");
@@ -457,7 +459,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 `Manager` find the object
/// 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
@@ -477,8 +479,8 @@ extern "C" fn C_GetAttributeValue(
let attr = unsafe { &*pTemplate.add(i) };
attr_types.push(attr.type_);
}
let mut manager_guard = try_to_get_manager_guard!();
let manager = manager_guard_to_manager!(manager_guard);
let mut manager_proxy_guard = try_to_get_manager_proxy_guard!();
let manager = manager_proxy_guard_to_manager!(manager_proxy_guard);
let values = match manager.get_attributes(hObject, attr_types) {
Ok(values) => values,
Err(e) => {
@@ -570,8 +572,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 `Manager` to start
/// the search.
/// 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,
@@ -600,8 +602,8 @@ extern "C" fn C_FindObjectsInit(
};
attrs.push((attr_type, slice.to_owned()));
}
let mut manager_guard = try_to_get_manager_guard!();
let manager = manager_guard_to_manager!(manager_guard);
let mut manager_proxy_guard = try_to_get_manager_proxy_guard!();
let manager = manager_proxy_guard_to_manager!(manager_proxy_guard);
match manager.start_search(hSession, attrs) {
Ok(()) => {}
Err(e) => {
@@ -614,7 +616,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 `Manager` and copying out the matching
/// 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,
@@ -626,8 +628,8 @@ extern "C" fn C_FindObjects(
log_with_thread_id!(error, "C_FindObjects: CKR_ARGUMENTS_BAD");
return CKR_ARGUMENTS_BAD;
}
let mut manager_guard = try_to_get_manager_guard!();
let manager = manager_guard_to_manager!(manager_guard);
let mut manager_proxy_guard = try_to_get_manager_proxy_guard!();
let manager = manager_proxy_guard_to_manager!(manager_proxy_guard);
let handles = match manager.search(hSession, ulMaxObjectCount as usize) {
Ok(handles) => handles,
Err(e) => {
@@ -655,10 +657,10 @@ extern "C" fn C_FindObjects(
}
/// This gets called after `C_FindObjectsInit` and `C_FindObjects` to finish a search. The module
/// tells the `Manager` to clear the search.
/// tells the `ManagerProxy` to clear the search.
extern "C" fn C_FindObjectsFinal(hSession: CK_SESSION_HANDLE) -> CK_RV {
let mut manager_guard = try_to_get_manager_guard!();
let manager = manager_guard_to_manager!(manager_guard);
let mut manager_proxy_guard = try_to_get_manager_proxy_guard!();
let manager = manager_proxy_guard_to_manager!(manager_proxy_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(()) => {
@@ -791,7 +793,8 @@ extern "C" fn C_DigestFinal(
CKR_FUNCTION_NOT_SUPPORTED
}
/// This gets called to set up a sign operation. The module essentially defers to the `Manager`.
/// This gets called to set up a sign operation. The module essentially defers to the
/// `ManagerProxy`.
extern "C" fn C_SignInit(
hSession: CK_SESSION_HANDLE,
pMechanism: CK_MECHANISM_PTR,
@@ -818,8 +821,8 @@ extern "C" fn C_SignInit(
} else {
None
};
let mut manager_guard = try_to_get_manager_guard!();
let manager = manager_guard_to_manager!(manager_guard);
let mut manager_proxy_guard = try_to_get_manager_proxy_guard!();
let manager = manager_proxy_guard_to_manager!(manager_proxy_guard);
match manager.start_sign(hSession, hKey, mechanism_params) {
Ok(()) => {}
Err(e) => {
@@ -833,7 +836,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
/// `Manager` and copies out the resulting signature.
/// `ManagerProxy` and copies out the resulting signature.
extern "C" fn C_Sign(
hSession: CK_SESSION_HANDLE,
pData: CK_BYTE_PTR,
@@ -847,8 +850,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_guard = try_to_get_manager_guard!();
let manager = manager_guard_to_manager!(manager_guard);
let mut manager_proxy_guard = try_to_get_manager_proxy_guard!();
let manager = manager_proxy_guard_to_manager!(manager_proxy_guard);
match manager.get_signature_length(hSession, data.to_vec()) {
Ok(signature_length) => unsafe {
*pulSignatureLen = signature_length as CK_ULONG;
@@ -859,8 +862,8 @@ extern "C" fn C_Sign(
}
}
} else {
let mut manager_guard = try_to_get_manager_guard!();
let manager = manager_guard_to_manager!(manager_guard);
let mut manager_proxy_guard = try_to_get_manager_proxy_guard!();
let manager = manager_proxy_guard_to_manager!(manager_proxy_guard);
match manager.sign(hSession, data.to_vec()) {
Ok(signature) => {
let signature_capacity = unsafe { *pulSignatureLen } as usize;

View File

@@ -5,6 +5,9 @@
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};
@@ -40,6 +43,281 @@ 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,