summaryrefslogtreecommitdiffstats
path: root/security/manager/ssl/osclientcerts/src/backend_macos.rs
diff options
context:
space:
mode:
Diffstat (limited to 'security/manager/ssl/osclientcerts/src/backend_macos.rs')
-rw-r--r--security/manager/ssl/osclientcerts/src/backend_macos.rs849
1 files changed, 849 insertions, 0 deletions
diff --git a/security/manager/ssl/osclientcerts/src/backend_macos.rs b/security/manager/ssl/osclientcerts/src/backend_macos.rs
new file mode 100644
index 0000000000..34386371aa
--- /dev/null
+++ b/security/manager/ssl/osclientcerts/src/backend_macos.rs
@@ -0,0 +1,849 @@
+/* -*- Mode: rust; rust-indent-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * 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");
+
+#[repr(C)]
+pub struct __SecIdentity(c_void);
+pub type SecIdentityRef = *const __SecIdentity;
+declare_TCFType!(SecIdentity, SecIdentityRef);
+impl_TCFType!(SecIdentity, SecIdentityRef, SecIdentityGetTypeID);
+
+#[repr(C)]
+pub struct __SecCertificate(c_void);
+pub type SecCertificateRef = *const __SecCertificate;
+declare_TCFType!(SecCertificate, SecCertificateRef);
+impl_TCFType!(SecCertificate, SecCertificateRef, SecCertificateGetTypeID);
+
+#[repr(C)]
+pub struct __SecKey(c_void);
+pub type SecKeyRef = *const __SecKey;
+declare_TCFType!(SecKey, SecKeyRef);
+impl_TCFType!(SecKey, SecKeyRef, SecKeyGetTypeID);
+
+#[repr(C)]
+pub struct __SecPolicy(c_void);
+pub type SecPolicyRef = *const __SecPolicy;
+declare_TCFType!(SecPolicy, SecPolicyRef);
+impl_TCFType!(SecPolicy, SecPolicyRef, SecPolicyGetTypeID);
+
+#[repr(C)]
+pub struct __SecTrust(c_void);
+pub type SecTrustRef = *const __SecTrust;
+declare_TCFType!(SecTrust, SecTrustRef);
+impl_TCFType!(SecTrust, SecTrustRef, SecTrustGetTypeID);
+
+type SecCertificateCopyKeyType = unsafe extern "C" fn(SecCertificateRef) -> SecKeyRef;
+type SecTrustEvaluateWithErrorType =
+ unsafe extern "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>,
+}
+
+lazy_static! {
+ static ref SECURITY_LIBRARY: Result<Library, String> = unsafe {
+ Library::new("/System/Library/Frameworks/Security.framework/Security")
+ .map_err(|e| e.to_string())
+ };
+}
+
+impl<'a> SecurityFramework<'a> {
+ fn new() -> Result<SecurityFramework<'a>, Error> {
+ let library = match &*SECURITY_LIBRARY {
+ Ok(library) => library,
+ Err(e) => return Err(error_here!(ErrorType::ExternalError, e.clone())),
+ };
+ let sec_certificate_copy_key = unsafe {
+ library
+ .get::<SecCertificateCopyKeyType>(b"SecCertificateCopyKey\0")
+ .map_err(|e| error_here!(ErrorType::ExternalError, e.to_string()))?
+ };
+ let sec_trust_evaluate_with_error = unsafe {
+ library
+ .get::<SecTrustEvaluateWithErrorType>(b"SecTrustEvaluateWithError\0")
+ .map_err(|e| error_here!(ErrorType::ExternalError, e.to_string()))?
+ };
+ let mut sec_string_constants = BTreeMap::new();
+ let strings_to_load = vec![
+ (
+ b"kSecKeyAlgorithmRSASignatureDigestPSSSHA1\0".as_ref(),
+ SecStringConstant::SecKeyAlgorithmRSASignatureDigestPSSSHA1,
+ ),
+ (
+ b"kSecKeyAlgorithmRSASignatureDigestPSSSHA256\0".as_ref(),
+ SecStringConstant::SecKeyAlgorithmRSASignatureDigestPSSSHA256,
+ ),
+ (
+ b"kSecKeyAlgorithmRSASignatureDigestPSSSHA384\0".as_ref(),
+ SecStringConstant::SecKeyAlgorithmRSASignatureDigestPSSSHA384,
+ ),
+ (
+ b"kSecKeyAlgorithmRSASignatureDigestPSSSHA512\0".as_ref(),
+ SecStringConstant::SecKeyAlgorithmRSASignatureDigestPSSSHA512,
+ ),
+ ];
+ for (symbol_name, sec_string_constant) in strings_to_load {
+ let cfstring_symbol = unsafe {
+ library
+ .get::<*const CFStringRef>(symbol_name)
+ .map_err(|e| error_here!(ErrorType::ExternalError, e.to_string()))?
+ };
+ let cfstring = unsafe { CFString::wrap_under_create_rule(**cfstring_symbol) };
+ sec_string_constants.insert(sec_string_constant, cfstring.to_string());
+ }
+ Ok(SecurityFramework {
+ sec_certificate_copy_key,
+ sec_trust_evaluate_with_error,
+ sec_string_constants,
+ })
+ }
+}
+
+struct SecurityFrameworkHolder<'a> {
+ framework: Result<SecurityFramework<'a>, Error>,
+}
+
+impl<'a> SecurityFrameworkHolder<'a> {
+ fn new() -> SecurityFrameworkHolder<'a> {
+ SecurityFrameworkHolder {
+ framework: SecurityFramework::new(),
+ }
+ }
+
+ /// SecCertificateCopyKey is available in macOS 10.14
+ fn sec_certificate_copy_key(&self, certificate: &SecCertificate) -> Result<SecKey, Error> {
+ match &self.framework {
+ Ok(framework) => unsafe {
+ let result =
+ (framework.sec_certificate_copy_key)(certificate.as_concrete_TypeRef());
+ if result.is_null() {
+ return Err(error_here!(ErrorType::ExternalError));
+ }
+ Ok(SecKey::wrap_under_create_rule(result))
+ },
+ Err(e) => Err(e.clone()),
+ }
+ }
+
+ /// SecTrustEvaluateWithError is available in macOS 10.14
+ fn sec_trust_evaluate_with_error(&self, trust: &SecTrust) -> Result<bool, Error> {
+ match &self.framework {
+ Ok(framework) => unsafe {
+ Ok((framework.sec_trust_evaluate_with_error)(
+ trust.as_concrete_TypeRef(),
+ std::ptr::null_mut(),
+ ))
+ },
+ Err(e) => Err(e.clone()),
+ }
+ }
+
+ fn get_sec_string_constant(
+ &self,
+ sec_string_constant: SecStringConstant,
+ ) -> Result<CFString, Error> {
+ match &self.framework {
+ Ok(framework) => match framework.sec_string_constants.get(&sec_string_constant) {
+ Some(string) => Ok(CFString::new(string)),
+ None => Err(error_here!(ErrorType::ExternalError)),
+ },
+ Err(e) => Err(e.clone()),
+ }
+ }
+}
+
+lazy_static! {
+ static ref SECURITY_FRAMEWORK: SecurityFrameworkHolder<'static> =
+ SecurityFrameworkHolder::new();
+}
+
+fn sec_key_create_signature(
+ key: &SecKey,
+ algorithm: SecKeyAlgorithm,
+ data: &CFData,
+) -> Result<CFData, Error> {
+ let mut error = std::ptr::null_mut();
+ let signature = unsafe {
+ SecKeyCreateSignature(
+ key.as_concrete_TypeRef(),
+ algorithm,
+ data.as_concrete_TypeRef(),
+ &mut error,
+ )
+ };
+ if signature.is_null() {
+ let error = unsafe { CFError::wrap_under_create_rule(error) };
+ return Err(error_here!(
+ ErrorType::ExternalError,
+ error.description().to_string()
+ ));
+ }
+ Ok(unsafe { CFData::wrap_under_create_rule(signature) })
+}
+
+fn sec_key_copy_attributes<T: TCFType>(key: &SecKey) -> CFDictionary<CFString, T> {
+ unsafe { CFDictionary::wrap_under_create_rule(SecKeyCopyAttributes(key.as_concrete_TypeRef())) }
+}
+
+fn sec_key_copy_external_representation(key: &SecKey) -> Result<CFData, Error> {
+ let mut error = std::ptr::null_mut();
+ let representation =
+ unsafe { SecKeyCopyExternalRepresentation(key.as_concrete_TypeRef(), &mut error) };
+ if representation.is_null() {
+ let error = unsafe { CFError::wrap_under_create_rule(error) };
+ return Err(error_here!(
+ ErrorType::ExternalError,
+ error.description().to_string()
+ ));
+ }
+ Ok(unsafe { CFData::wrap_under_create_rule(representation) })
+}
+
+fn sec_identity_copy_certificate(identity: &SecIdentity) -> Result<SecCertificate, Error> {
+ let mut certificate = std::ptr::null();
+ let status =
+ unsafe { SecIdentityCopyCertificate(identity.as_concrete_TypeRef(), &mut certificate) };
+ if status != errSecSuccess {
+ return Err(error_here!(ErrorType::ExternalError, status.to_string()));
+ }
+ if certificate.is_null() {
+ return Err(error_here!(ErrorType::ExternalError));
+ }
+ Ok(unsafe { SecCertificate::wrap_under_create_rule(certificate) })
+}
+
+fn sec_certificate_copy_subject_summary(certificate: &SecCertificate) -> Result<CFString, Error> {
+ let result = unsafe { SecCertificateCopySubjectSummary(certificate.as_concrete_TypeRef()) };
+ if result.is_null() {
+ return Err(error_here!(ErrorType::ExternalError));
+ }
+ Ok(unsafe { CFString::wrap_under_create_rule(result) })
+}
+
+fn sec_certificate_copy_data(certificate: &SecCertificate) -> Result<CFData, Error> {
+ let result = unsafe { SecCertificateCopyData(certificate.as_concrete_TypeRef()) };
+ if result.is_null() {
+ return Err(error_here!(ErrorType::ExternalError));
+ }
+ Ok(unsafe { CFData::wrap_under_create_rule(result) })
+}
+
+fn sec_identity_copy_private_key(identity: &SecIdentity) -> Result<SecKey, Error> {
+ let mut key = std::ptr::null();
+ let status = unsafe { SecIdentityCopyPrivateKey(identity.as_concrete_TypeRef(), &mut key) };
+ if status != errSecSuccess {
+ return Err(error_here!(ErrorType::ExternalError));
+ }
+ if key.is_null() {
+ return Err(error_here!(ErrorType::ExternalError));
+ }
+ Ok(unsafe { SecKey::wrap_under_create_rule(key) })
+}
+
+pub struct Cert {
+ class: Vec<u8>,
+ token: Vec<u8>,
+ id: Vec<u8>,
+ label: Vec<u8>,
+ value: Vec<u8>,
+ issuer: Vec<u8>,
+ serial_number: Vec<u8>,
+ subject: Vec<u8>,
+}
+
+impl Cert {
+ fn new_from_identity(identity: &SecIdentity) -> Result<Cert, Error> {
+ let certificate = sec_identity_copy_certificate(identity)?;
+ Cert::new_from_certificate(&certificate)
+ }
+
+ fn new_from_certificate(certificate: &SecCertificate) -> Result<Cert, Error> {
+ let label = sec_certificate_copy_subject_summary(certificate)?;
+ let der = sec_certificate_copy_data(certificate)?;
+ let der = der.bytes().to_vec();
+ let id = Sha256::digest(&der).to_vec();
+ let (serial_number, issuer, subject) = read_encoded_certificate_identifiers(&der)?;
+ Ok(Cert {
+ class: serialize_uint(CKO_CERTIFICATE)?,
+ token: serialize_uint(CK_TRUE)?,
+ id,
+ label: label.to_string().into_bytes(),
+ value: der,
+ issuer,
+ serial_number,
+ subject,
+ })
+ }
+
+ fn class(&self) -> &[u8] {
+ &self.class
+ }
+
+ fn token(&self) -> &[u8] {
+ &self.token
+ }
+
+ fn id(&self) -> &[u8] {
+ &self.id
+ }
+
+ fn label(&self) -> &[u8] {
+ &self.label
+ }
+
+ fn value(&self) -> &[u8] {
+ &self.value
+ }
+
+ fn issuer(&self) -> &[u8] {
+ &self.issuer
+ }
+
+ fn serial_number(&self) -> &[u8] {
+ &self.serial_number
+ }
+
+ fn subject(&self) -> &[u8] {
+ &self.subject
+ }
+}
+
+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 {
+ return false;
+ }
+ 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(),
+ _ => return false,
+ };
+ if attr_value.as_slice() != comparison {
+ return false;
+ }
+ }
+ true
+ }
+
+ fn get_attribute(&self, attribute: CK_ATTRIBUTE_TYPE) -> Option<&[u8]> {
+ let result = match attribute {
+ 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(),
+ _ => return None,
+ };
+ Some(result)
+ }
+}
+
+#[allow(clippy::upper_case_acronyms)]
+#[derive(Clone, Copy, Debug)]
+pub enum KeyType {
+ EC(usize),
+ RSA,
+}
+
+#[allow(clippy::upper_case_acronyms)]
+enum SignParams<'a> {
+ EC(CFString, &'a [u8]),
+ RSA(CFString, &'a [u8]),
+}
+
+impl<'a> SignParams<'a> {
+ fn new(
+ key_type: KeyType,
+ data: &'a [u8],
+ params: &Option<CK_RSA_PKCS_PSS_PARAMS>,
+ ) -> Result<SignParams<'a>, Error> {
+ match key_type {
+ KeyType::EC(_) => SignParams::new_ec_params(data),
+ KeyType::RSA => SignParams::new_rsa_params(params, data),
+ }
+ }
+
+ fn new_ec_params(data: &'a [u8]) -> Result<SignParams<'a>, Error> {
+ let algorithm = unsafe {
+ CFString::wrap_under_get_rule(match data.len() {
+ 20 => kSecKeyAlgorithmECDSASignatureDigestX962SHA1,
+ 32 => kSecKeyAlgorithmECDSASignatureDigestX962SHA256,
+ 48 => kSecKeyAlgorithmECDSASignatureDigestX962SHA384,
+ 64 => kSecKeyAlgorithmECDSASignatureDigestX962SHA512,
+ _ => {
+ return Err(error_here!(ErrorType::UnsupportedInput));
+ }
+ })
+ };
+ Ok(SignParams::EC(algorithm, data))
+ }
+
+ fn new_rsa_params(
+ params: &Option<CK_RSA_PKCS_PSS_PARAMS>,
+ data: &'a [u8],
+ ) -> Result<SignParams<'a>, Error> {
+ if let Some(pss_params) = params {
+ let algorithm = {
+ let algorithm_id = match pss_params.hashAlg {
+ CKM_SHA_1 => SecStringConstant::SecKeyAlgorithmRSASignatureDigestPSSSHA1,
+ CKM_SHA256 => SecStringConstant::SecKeyAlgorithmRSASignatureDigestPSSSHA256,
+ CKM_SHA384 => SecStringConstant::SecKeyAlgorithmRSASignatureDigestPSSSHA384,
+ CKM_SHA512 => SecStringConstant::SecKeyAlgorithmRSASignatureDigestPSSSHA512,
+ _ => {
+ return Err(error_here!(ErrorType::UnsupportedInput));
+ }
+ };
+ SECURITY_FRAMEWORK.get_sec_string_constant(algorithm_id)?
+ };
+ return Ok(SignParams::RSA(algorithm, data));
+ }
+
+ // Handle the case where this 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` should be a DigestInfo.
+ let (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)),
+ })
+ };
+
+ Ok(SignParams::RSA(algorithm, hash))
+ }
+
+ fn get_algorithm(&self) -> SecKeyAlgorithm {
+ match self {
+ SignParams::EC(algorithm, _) => algorithm.as_concrete_TypeRef(),
+ SignParams::RSA(algorithm, _) => algorithm.as_concrete_TypeRef(),
+ }
+ }
+
+ fn get_data_to_sign(&self) -> &'a [u8] {
+ match self {
+ SignParams::EC(_, data_to_sign) => data_to_sign,
+ SignParams::RSA(_, data_to_sign) => data_to_sign,
+ }
+ }
+}
+
+pub struct Key {
+ identity: SecIdentity,
+ class: Vec<u8>,
+ token: Vec<u8>,
+ id: Vec<u8>,
+ private: Vec<u8>,
+ key_type: Vec<u8>,
+ 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> {
+ 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 })?;
+ let mut modulus = None;
+ let mut 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)
+ } else if 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));
+ };
+
+ Ok(Key {
+ identity: identity.clone(),
+ class: serialize_uint(CKO_PRIVATE_KEY)?,
+ token: serialize_uint(CK_TRUE)?,
+ id,
+ private: serialize_uint(CK_TRUE)?,
+ key_type: serialize_uint(key_type_attribute)?,
+ modulus,
+ ec_params,
+ key_type_enum,
+ key_handle: None,
+ })
+ }
+
+ fn class(&self) -> &[u8] {
+ &self.class
+ }
+
+ fn token(&self) -> &[u8] {
+ &self.token
+ }
+
+ fn id(&self) -> &[u8] {
+ &self.id
+ }
+
+ fn private(&self) -> &[u8] {
+ &self.private
+ }
+
+ fn key_type(&self) -> &[u8] {
+ &self.key_type
+ }
+
+ fn modulus(&self) -> Option<&[u8]> {
+ match &self.modulus {
+ Some(modulus) => Some(modulus.as_slice()),
+ None => None,
+ }
+ }
+
+ fn ec_params(&self) -> Option<&[u8]> {
+ match &self.ec_params {
+ Some(ec_params) => Some(ec_params.as_slice()),
+ 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.
+ let (r, s) = read_ec_sig_point(signature.bytes())?;
+ if r.len() > coordinate_width || s.len() > coordinate_width {
+ return Err(error_here!(ErrorType::InvalidInput));
+ }
+ let mut 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 {
+ return false;
+ }
+ 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 => {
+ if let Some(modulus) = self.modulus() {
+ modulus
+ } else {
+ return false;
+ }
+ }
+ CKA_EC_PARAMS => {
+ if let Some(ec_params) = self.ec_params() {
+ ec_params
+ } else {
+ return false;
+ }
+ }
+ _ => return false,
+ };
+ if attr_value.as_slice() != comparison {
+ return false;
+ }
+ }
+ true
+ }
+
+ fn get_attribute(&self, attribute: CK_ATTRIBUTE_TYPE) -> Option<&[u8]> {
+ match attribute {
+ CKA_CLASS => Some(self.class()),
+ CKA_TOKEN => Some(self.token()),
+ CKA_ID => Some(self.id()),
+ CKA_PRIVATE => Some(self.private()),
+ CKA_KEY_TYPE => Some(self.key_type()),
+ CKA_MODULUS => self.modulus(),
+ CKA_EC_PARAMS => self.ec_params(),
+ _ => None,
+ }
+ }
+}
+
+impl Sign for Key {
+ fn get_signature_length(
+ &mut self,
+ 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(
+ &mut self,
+ 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)
+ }
+}
+
+fn get_key_attribute<T: TCFType + Clone>(key: &SecKey, attr: CFStringRef) -> Result<T, Error> {
+ let attributes: CFDictionary<CFString, T> = sec_key_copy_attributes(key);
+ match attributes.find(attr as *const _) {
+ Some(value) => Ok((*value).clone()),
+ None => Err(error_here!(ErrorType::ExternalError)),
+ }
+}
+
+// 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) };
+ let mut 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()) };
+ let mut certificates = Vec::with_capacity(
+ certificate_count
+ .try_into()
+ .map_err(|_| error_here!(ErrorType::ValueTooLarge))?,
+ );
+ for i in 1..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)
+}
+
+pub struct Backend {}
+
+impl ClientCertsBackend for Backend {
+ type Cert = Cert;
+ type Key = Key;
+
+ fn find_objects(&self) -> 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);
+ 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))
+ }
+}