/* 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 log::error; use pkcs11_bindings::*; use rsclientcerts::cryptoki::*; use rsclientcerts::manager::{ClientCertsBackend, CryptokiObject, Sign}; use rsclientcerts_util::*; use rsclientcerts_util::error::{Error, ErrorType}; use std::collections::BTreeMap; use std::convert::TryInto; use std::os::raw::c_void; use std::time::{Duration, Instant}; 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, // etc.. This is easier.
include!("bindings_macos.rs");
/// 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. unsafeimpl Send for SecIdentity {}
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>,
}
// Handle the case where `data` is a DigestInfo. iflet Ok((digest_oid, hash)) = read_digest_info(data) { let algorithm = unsafe {
CFString::wrap_under_create_rule(match digest_oid {
OID_BYTES_SHA_256 => kSecKeyAlgorithmRSASignatureDigestPKCS1v15SHA256,
OID_BYTES_SHA_384 => kSecKeyAlgorithmRSASignatureDigestPKCS1v15SHA384,
OID_BYTES_SHA_512 => kSecKeyAlgorithmRSASignatureDigestPKCS1v15SHA512,
OID_BYTES_SHA_1 => kSecKeyAlgorithmRSASignatureDigestPKCS1v15SHA1,
_ => return Err(error_here!(ErrorType::UnsupportedInput)),
})
}; return Ok(SignParams::RSA(algorithm, hash));
}
// Handle the case where `data` is a TLS 1.0 MD5/SHA1 hash. if data.len() == 36 { let algorithm = unsafe {
CFString::wrap_under_get_rule(kSecKeyAlgorithmRSASignatureDigestPKCS1v15Raw)
}; return Ok(SignParams::RSA(algorithm, data));
}
// Otherwise, `data` will be an emsa-pss-encoded digest that should be signed with raw RSA.
Ok(SignParams::RSA( unsafe { CFString::wrap_under_create_rule(kSecKeyAlgorithmRSASignatureRaw) },
data,
))
}
/// 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>,
}
fn sign(
&mutself,
key_type: 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));
}; letmut 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, asyncmove { let result = sign_internal(&identity, &mut maybe_key, key_type, &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, &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,
&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: 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, 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 {
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(&mutself) { // 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(); // It is possible that we're already on the appropriate thread (e.g. if an error was // encountered in `find_objects` and these handles are being released shortly after being // created). if moz_task::is_on_current_thread(&thread) { // `key` is obtained from `identity`, so drop it first, out of an abundance of caution.
drop(key);
drop(identity);
} else { let task = moz_task::spawn_onto("drop", &thread, asyncmove {
drop(key);
drop(identity);
});
futures_executor::block_on(task)
}
}
}
impl Key { fn new(identity: &SecIdentity, thread: &nsIEventTarget) -> Result<Key, Error> { let certificate = sec_identity_copy_certificate(identity)?; let der = sec_certificate_copy_data(&certificate)?; 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 })?; let sec_attr_key_type_ec = unsafe { CFString::wrap_under_create_rule(kSecAttrKeyTypeECSECPrimeRandom) }; let (modulus, ec_params) = if key_type.as_concrete_TypeRef() == unsafe { kSecAttrKeyTypeRSA } { let public_key = sec_key_copy_external_representation(&key)?; let modulus = read_rsa_modulus(public_key.bytes())?;
(Some(modulus), None)
} 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)),
}; let ec_params = match key_size_in_bits { 256 => ENCODED_OID_BYTES_SECP256R1.to_vec(), 384 => ENCODED_OID_BYTES_SECP384R1.to_vec(), 521 => ENCODED_OID_BYTES_SECP521R1.to_vec(),
_ => return Err(error_here!(ErrorType::UnsupportedInput)),
};
(None, Some(ec_params))
} else { return Err(error_here!(ErrorType::LibraryFailure));
};
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> { self.handles.sign( self.cryptoki_key.key_type(), self.cryptoki_key.modulus().clone(),
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 { /// 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>, /// The last time a call to `find_objects` finished, to avoid searching for objects more than /// once every 3 seconds.
last_scan_finished: Option<Instant>,
}
let thread = self.thread.clone(); let task = moz_task::spawn_onto("find_objects", &self.thread, asyncmove {
find_objects(&thread)
}); let result = futures_executor::block_on(task); self.last_scan_finished = Some(Instant::now());
result
}
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.