/* -*- Mode: rust; rust-indent-offset: 4 -*- */ /* This Source Code Form is subject to the terms of the Mozilla Public *License,v.2.0.IfacopyoftheMPLwasnotdistributedwiththis
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#![allow(non_upper_case_globals)]
use core_foundation::array::*; use core_foundation::base::*; use core_foundation::boolean::*; use core_foundation::data::*; use core_foundation::dictionary::*; use core_foundation::error::*; use core_foundation::number::*; use core_foundation::string::*; use libloading::{Library, Symbol}; use pkcs11_bindings::*; use rsclientcerts::error::{Error, ErrorType}; use rsclientcerts::manager::{ClientCertsBackend, CryptokiObject, Sign, SlotType}; use rsclientcerts::util::*; use sha2::{Digest, Sha256}; use std::collections::BTreeMap; use std::convert::TryInto; use std::os::raw::c_void;
// 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, // etc.. This is easier.
include!("bindings_macos.rs");
type SecCertificateCopyKeyType = unsafeextern"C"fn(SecCertificateRef) -> SecKeyRef; type SecTrustEvaluateWithErrorType = unsafeextern"C"fn(trust: SecTrustRef, error: *mut CFErrorRef) -> bool;
#[derive(Ord, Eq, PartialOrd, PartialEq)] enum SecStringConstant { // These are available in macOS 10.13
SecKeyAlgorithmRSASignatureDigestPSSSHA1,
SecKeyAlgorithmRSASignatureDigestPSSSHA256,
SecKeyAlgorithmRSASignatureDigestPSSSHA384,
SecKeyAlgorithmRSASignatureDigestPSSSHA512,
}
/// This implementation uses security framework functions and constants that /// are not provided by the version of the SDK we build with. To work around /// this, we attempt to open and dynamically load these functions and symbols /// at runtime. Unfortunately this does mean that if a user is not on a new /// enough version of macOS, they will not be able to use client certificates /// from their keychain in Firefox until they upgrade. struct SecurityFramework<'a> {
sec_certificate_copy_key: Symbol<'a, SecCertificateCopyKeyType>,
sec_trust_evaluate_with_error: Symbol<'a, SecTrustEvaluateWithErrorType>,
sec_string_constants: BTreeMap<SecStringConstant, String>,
}
impl CryptokiObject for Cert { fn matches(&self, slot_type: SlotType, attrs: &[(CK_ATTRIBUTE_TYPE, Vec<u8>)]) -> bool { // The modern/legacy slot distinction in theory enables differentiation // between keys that are from modules that can use modern cryptography // (namely EC keys and RSA-PSS signatures) and those that cannot. // However, the function that would enable this // (SecKeyIsAlgorithmSupported) causes a password dialog to appear on // our test machines, so this backend pretends that everything supports // modern crypto for now. if slot_type != SlotType::Modern { returnfalse;
} for (attr_type, attr_value) in attrs { let comparison = match *attr_type {
CKA_CLASS => self.class(),
CKA_TOKEN => self.token(),
CKA_LABEL => self.label(),
CKA_ID => self.id(),
CKA_VALUE => self.value(),
CKA_ISSUER => self.issuer(),
CKA_SERIAL_NUMBER => self.serial_number(),
CKA_SUBJECT => self.subject(),
_ => returnfalse,
}; if attr_value.as_slice() != comparison { returnfalse;
}
} true
}
impl Key { 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(); let key = SECURITY_FRAMEWORK.sec_certificate_copy_key(&certificate)?; let key_type: CFString = get_key_attribute(&key, unsafe { kSecAttrKeyType })?; let key_size_in_bits: CFNumber = get_key_attribute(&key, unsafe { kSecAttrKeySizeInBits })?; letmut modulus = None; letmut ec_params = None; let sec_attr_key_type_ec = unsafe { CFString::wrap_under_create_rule(kSecAttrKeyTypeECSECPrimeRandom) }; let (key_type_enum, key_type_attribute) = if key_type.as_concrete_TypeRef() == unsafe { kSecAttrKeyTypeRSA } { let public_key = sec_key_copy_external_representation(&key)?; let modulus_value = read_rsa_modulus(public_key.bytes())?;
modulus = Some(modulus_value);
(KeyType::RSA, CKK_RSA)
} elseif key_type == sec_attr_key_type_ec { // Assume all EC keys are secp256r1, secp384r1, or secp521r1. This // is wrong, but the API doesn't seem to give us a way to determine // which curve this key is on. // This might not matter in practice, because it seems all NSS uses // this for is to get the signature size. let key_size_in_bits = match key_size_in_bits.to_i64() {
Some(value) => value,
None => return Err(error_here!(ErrorType::ValueTooLarge)),
}; match key_size_in_bits { 256 => ec_params = Some(ENCODED_OID_BYTES_SECP256R1.to_vec()), 384 => ec_params = Some(ENCODED_OID_BYTES_SECP384R1.to_vec()), 521 => ec_params = Some(ENCODED_OID_BYTES_SECP521R1.to_vec()),
_ => return Err(error_here!(ErrorType::UnsupportedInput)),
} let coordinate_width = (key_size_in_bits as usize + 7) / 8;
(KeyType::EC(coordinate_width), CKK_EC)
} else { return Err(error_here!(ErrorType::LibraryFailure));
};
fn sign_internal(
&mutself,
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. ifself.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 = matchself.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. let (r, s) = read_ec_sig_point(signature.bytes())?; if r.len() > coordinate_width || s.len() > coordinate_width { return Err(error_here!(ErrorType::InvalidInput));
} letmut signature_value = Vec::with_capacity(2 * coordinate_width); let r_padding = vec![0; coordinate_width - r.len()];
signature_value.extend(r_padding);
signature_value.extend_from_slice(r); let s_padding = vec![0; coordinate_width - s.len()];
signature_value.extend(s_padding);
signature_value.extend_from_slice(s);
signature_value
}
KeyType::RSA => signature.bytes().to_vec(),
};
Ok(signature_value)
}
}
impl CryptokiObject for Key { fn matches(&self, slot_type: SlotType, attrs: &[(CK_ATTRIBUTE_TYPE, Vec<u8>)]) -> bool { // The modern/legacy slot distinction in theory enables differentiation // between keys that are from modules that can use modern cryptography // (namely EC keys and RSA-PSS signatures) and those that cannot. // However, the function that would enable this // (SecKeyIsAlgorithmSupported) causes a password dialog to appear on // our test machines, so this backend pretends that everything supports // modern crypto for now. if slot_type != SlotType::Modern { returnfalse;
} for (attr_type, attr_value) in attrs { let comparison = match *attr_type {
CKA_CLASS => self.class(),
CKA_TOKEN => self.token(),
CKA_ID => self.id(),
CKA_PRIVATE => self.private(),
CKA_KEY_TYPE => self.key_type(),
CKA_MODULUS => { iflet Some(modulus) = self.modulus() {
modulus
} else { returnfalse;
}
}
CKA_EC_PARAMS => { iflet Some(ec_params) = self.ec_params() {
ec_params
} else { returnfalse;
}
}
_ => returnfalse,
}; if attr_value.as_slice() != comparison { returnfalse;
}
} true
}
impl Sign for Key { fn get_signature_length(
&mutself,
data: &[u8],
params: &Option<CK_RSA_PKCS_PSS_PARAMS>,
) -> Result<usize, Error> { // Unfortunately we don't have a way of getting the length of a signature without creating // one. let dummy_signature_bytes = self.sign(data, params)?;
Ok(dummy_signature_bytes.len())
}
// The input data is a hash. What algorithm we use depends on the size of the hash. fn sign(
&mutself,
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(); self.sign_internal(data, params)
}
}
// Given a SecIdentity, attempts to build as much of a path to a trust anchor as possible, gathers // the CA certificates from that path, and returns them. The purpose of this function is not to // validate the given certificate but to find CA certificates that gecko may need to do path // building when filtering client certificates according to the acceptable CA list sent by the // server during client authentication. fn get_issuers(identity: &SecIdentity) -> Result<Vec<SecCertificate>, Error> { let certificate = sec_identity_copy_certificate(identity)?; let policy = unsafe { SecPolicyCreateSSL(false, std::ptr::null()) }; if policy.is_null() { return Err(error_here!(ErrorType::ExternalError));
} let policy = unsafe { SecPolicy::wrap_under_create_rule(policy) }; letmut trust = std::ptr::null(); // Each of SecTrustCreateWithCertificates' input arguments can be either single items or an // array of items. Since we only want to specify one of each, we directly specify the arguments. let status = unsafe {
SecTrustCreateWithCertificates(
certificate.as_concrete_TypeRef(),
policy.as_concrete_TypeRef(),
&mut trust,
)
}; if status != errSecSuccess { return Err(error_here!(ErrorType::ExternalError));
} if trust.is_null() { return Err(error_here!(ErrorType::ExternalError));
} let trust = unsafe { SecTrust::wrap_under_create_rule(trust) }; // Disable AIA fetching so that SecTrustEvaluateWithError doesn't result in network I/O. let status = unsafe { SecTrustSetNetworkFetchAllowed(trust.as_concrete_TypeRef(), 0) }; if status != errSecSuccess { return Err(error_here!(ErrorType::ExternalError));
} // We ignore the return value here because we don't care if the certificate is trusted or not - // we're only doing this to build its issuer chain as much as possible. let _ = SECURITY_FRAMEWORK.sec_trust_evaluate_with_error(&trust)?; let certificate_count = unsafe { SecTrustGetCertificateCount(trust.as_concrete_TypeRef()) }; letmut certificates = Vec::with_capacity(
certificate_count
.try_into()
.map_err(|_| error_here!(ErrorType::ValueTooLarge))?,
); for i in1..certificate_count { let certificate = unsafe { SecTrustGetCertificateAtIndex(trust.as_concrete_TypeRef(), i) }; if certificate.is_null() {
error!("SecTrustGetCertificateAtIndex returned null certificate?"); continue;
} let certificate = unsafe { SecCertificate::wrap_under_get_rule(certificate) };
certificates.push(certificate);
}
Ok(certificates)
}
pubstruct Backend {}
impl ClientCertsBackend for Backend { type Cert = Cert; type Key = Key;
fn find_objects(&self) -> Result<(Vec<Cert>, Vec<Key>), Error> { letmut certs = Vec::new(); letmut 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); letmut 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); iflet (Ok(cert), Ok(key)) = (cert, key) {
certs.push(cert);
keys.push(key);
} else { continue;
} iflet Ok(issuers) = get_issuers(&identity) { for issuer in issuers { iflet Ok(cert) = Cert::new_from_certificate(&issuer) {
certs.push(cert);
}
}
}
}
Ok((certs, keys))
}
}
Messung V0.5 in Prozent
¤ Dauer der Verarbeitung: 0.18 Sekunden
(vorverarbeitet am 2026-06-19)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.