diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
commit | 43a97878ce14b72f0981164f87f2e35e14151312 (patch) | |
tree | 620249daf56c0258faa40cbdcf9cfba06de2a846 /security/manager/ssl/osclientcerts/src | |
parent | Initial commit. (diff) | |
download | firefox-43a97878ce14b72f0981164f87f2e35e14151312.tar.xz firefox-43a97878ce14b72f0981164f87f2e35e14151312.zip |
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'security/manager/ssl/osclientcerts/src')
-rw-r--r-- | security/manager/ssl/osclientcerts/src/backend_macos.rs | 849 | ||||
-rw-r--r-- | security/manager/ssl/osclientcerts/src/backend_windows.rs | 914 | ||||
-rw-r--r-- | security/manager/ssl/osclientcerts/src/bindings_macos.rs | 79 | ||||
-rw-r--r-- | security/manager/ssl/osclientcerts/src/lib.rs | 1218 |
4 files changed, 3060 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)) + } +} diff --git a/security/manager/ssl/osclientcerts/src/backend_windows.rs b/security/manager/ssl/osclientcerts/src/backend_windows.rs new file mode 100644 index 0000000000..2a80ff8354 --- /dev/null +++ b/security/manager/ssl/osclientcerts/src/backend_windows.rs @@ -0,0 +1,914 @@ +/* -*- 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_camel_case_types)] + +use pkcs11_bindings::*; +use rsclientcerts::error::{Error, ErrorType}; +use rsclientcerts::manager::{ClientCertsBackend, CryptokiObject, Sign, SlotType}; +use rsclientcerts::util::*; +use sha2::{Digest, Sha256}; +use std::convert::TryInto; +use std::ffi::{c_void, CStr, CString}; +use std::ops::Deref; +use std::slice; +use winapi::shared::bcrypt::*; +use winapi::shared::minwindef::{DWORD, PBYTE}; +use winapi::um::errhandlingapi::GetLastError; +use winapi::um::ncrypt::*; +use winapi::um::wincrypt::{HCRYPTHASH, HCRYPTPROV, *}; + +// winapi has some support for ncrypt.h, but not for this function. +extern "system" { + fn NCryptSignHash( + hKey: NCRYPT_KEY_HANDLE, + pPaddingInfo: *mut c_void, + pbHashValue: PBYTE, + cbHashValue: DWORD, + pbSignature: PBYTE, + cbSignature: DWORD, + pcbResult: *mut DWORD, + dwFlags: DWORD, + ) -> SECURITY_STATUS; +} + +/// Given a `CERT_INFO`, tries to return the bytes of the subject distinguished name as formatted by +/// `CertNameToStrA` using the flag `CERT_SIMPLE_NAME_STR`. This is used as the label for the +/// certificate. +fn get_cert_subject_dn(cert_info: &CERT_INFO) -> Result<Vec<u8>, Error> { + let mut cert_info_subject = cert_info.Subject; + let subject_dn_len = unsafe { + CertNameToStrA( + X509_ASN_ENCODING, + &mut cert_info_subject, + CERT_SIMPLE_NAME_STR, + std::ptr::null_mut(), + 0, + ) + }; + // subject_dn_len includes the terminating null byte. + let mut subject_dn_string_bytes: Vec<u8> = vec![0; subject_dn_len as usize]; + let subject_dn_len = unsafe { + CertNameToStrA( + X509_ASN_ENCODING, + &mut cert_info_subject, + CERT_SIMPLE_NAME_STR, + subject_dn_string_bytes.as_mut_ptr() as *mut i8, + subject_dn_string_bytes + .len() + .try_into() + .map_err(|_| error_here!(ErrorType::ValueTooLarge))?, + ) + }; + if subject_dn_len as usize != subject_dn_string_bytes.len() { + return Err(error_here!(ErrorType::ExternalError)); + } + Ok(subject_dn_string_bytes) +} + +/// Represents a certificate for which there exists a corresponding private key. +pub struct Cert { + /// PKCS #11 object class. Will be `CKO_CERTIFICATE`. + class: Vec<u8>, + /// Whether or not this is on a token. Will be `CK_TRUE`. + token: Vec<u8>, + /// An identifier unique to this certificate. Must be the same as the ID for the private key. + id: Vec<u8>, + /// The bytes of a human-readable label for this certificate. Will be the subject DN. + label: Vec<u8>, + /// The DER bytes of the certificate. + value: Vec<u8>, + /// The DER bytes of the issuer distinguished name of the certificate. + issuer: Vec<u8>, + /// The DER bytes of the serial number of the certificate. + serial_number: Vec<u8>, + /// The DER bytes of the subject distinguished name of the certificate. + subject: Vec<u8>, + /// Which slot this certificate should be exposed on. + slot_type: SlotType, +} + +impl Cert { + fn new(cert_context: PCCERT_CONTEXT) -> Result<Cert, Error> { + let cert = unsafe { &*cert_context }; + let cert_info = unsafe { &*cert.pCertInfo }; + let value = + unsafe { slice::from_raw_parts(cert.pbCertEncoded, cert.cbCertEncoded as usize) }; + let value = value.to_vec(); + let id = Sha256::digest(&value).to_vec(); + let label = get_cert_subject_dn(cert_info)?; + let (serial_number, issuer, subject) = read_encoded_certificate_identifiers(&value)?; + Ok(Cert { + class: serialize_uint(CKO_CERTIFICATE)?, + token: serialize_uint(CK_TRUE)?, + id, + label, + value, + issuer, + serial_number, + subject, + slot_type: SlotType::Modern, + }) + } + + 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 { + if slot_type != self.slot_type { + 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) + } +} + +struct CertContext(PCCERT_CONTEXT); + +impl CertContext { + fn new(cert: PCCERT_CONTEXT) -> CertContext { + CertContext(unsafe { CertDuplicateCertificateContext(cert) }) + } +} + +impl Drop for CertContext { + fn drop(&mut self) { + unsafe { + CertFreeCertificateContext(self.0); + } + } +} + +impl Deref for CertContext { + type Target = PCCERT_CONTEXT; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +enum KeyHandle { + NCrypt(NCRYPT_KEY_HANDLE), + CryptoAPI(HCRYPTPROV, DWORD), +} + +impl KeyHandle { + fn from_cert(cert: &CertContext) -> Result<KeyHandle, Error> { + let mut key_handle = 0; + let mut key_spec = 0; + let mut must_free = 0; + unsafe { + if CryptAcquireCertificatePrivateKey( + **cert, + CRYPT_ACQUIRE_PREFER_NCRYPT_KEY_FLAG, + std::ptr::null_mut(), + &mut key_handle, + &mut key_spec, + &mut must_free, + ) != 1 + { + return Err(error_here!( + ErrorType::ExternalError, + GetLastError().to_string() + )); + } + } + if must_free == 0 { + return Err(error_here!(ErrorType::ExternalError)); + } + if key_spec == CERT_NCRYPT_KEY_SPEC { + Ok(KeyHandle::NCrypt(key_handle as NCRYPT_KEY_HANDLE)) + } else { + Ok(KeyHandle::CryptoAPI(key_handle as HCRYPTPROV, key_spec)) + } + } + + fn sign( + &self, + data: &[u8], + params: &Option<CK_RSA_PKCS_PSS_PARAMS>, + do_signature: bool, + key_type: KeyType, + ) -> Result<Vec<u8>, Error> { + match &self { + KeyHandle::NCrypt(ncrypt_handle) => { + sign_ncrypt(ncrypt_handle, data, params, do_signature, key_type) + } + KeyHandle::CryptoAPI(hcryptprov, key_spec) => { + sign_cryptoapi(hcryptprov, key_spec, data, params, do_signature) + } + } + } +} + +impl Drop for KeyHandle { + fn drop(&mut self) { + match self { + KeyHandle::NCrypt(ncrypt_handle) => unsafe { + let _ = NCryptFreeObject(*ncrypt_handle); + }, + KeyHandle::CryptoAPI(hcryptprov, _) => unsafe { + let _ = CryptReleaseContext(*hcryptprov, 0); + }, + } + } +} + +fn sign_ncrypt( + ncrypt_handle: &NCRYPT_KEY_HANDLE, + data: &[u8], + params: &Option<CK_RSA_PKCS_PSS_PARAMS>, + do_signature: bool, + key_type: KeyType, +) -> Result<Vec<u8>, Error> { + let mut sign_params = SignParams::new(key_type, params)?; + let params_ptr = sign_params.params_ptr(); + let flags = sign_params.flags(); + let mut data = data.to_vec(); + let mut signature_len = 0; + // We call NCryptSignHash twice: the first time to get the size of the buffer we need to + // allocate and then again to actually sign the data, if `do_signature` is `true`. + let status = unsafe { + NCryptSignHash( + *ncrypt_handle, + params_ptr, + data.as_mut_ptr(), + data.len() + .try_into() + .map_err(|_| error_here!(ErrorType::ValueTooLarge))?, + std::ptr::null_mut(), + 0, + &mut signature_len, + flags, + ) + }; + // 0 is "ERROR_SUCCESS" (but "ERROR_SUCCESS" is unsigned, whereas SECURITY_STATUS is signed) + if status != 0 { + return Err(error_here!(ErrorType::ExternalError, status.to_string())); + } + let mut signature = vec![0; signature_len as usize]; + if !do_signature { + return Ok(signature); + } + let mut final_signature_len = signature_len; + let status = unsafe { + NCryptSignHash( + *ncrypt_handle, + params_ptr, + data.as_mut_ptr(), + data.len() + .try_into() + .map_err(|_| error_here!(ErrorType::ValueTooLarge))?, + signature.as_mut_ptr(), + signature_len, + &mut final_signature_len, + flags, + ) + }; + if status != 0 { + return Err(error_here!(ErrorType::ExternalError, status.to_string())); + } + if final_signature_len != signature_len { + return Err(error_here!(ErrorType::ExternalError)); + } + Ok(signature) +} + +fn sign_cryptoapi( + hcryptprov: &HCRYPTPROV, + key_spec: &DWORD, + data: &[u8], + params: &Option<CK_RSA_PKCS_PSS_PARAMS>, + do_signature: bool, +) -> Result<Vec<u8>, Error> { + if params.is_some() { + return Err(error_here!(ErrorType::LibraryFailure)); + } + // data will be an encoded DigestInfo, which specifies the hash algorithm and bytes of the hash + // to sign. However, CryptoAPI requires directly specifying the bytes of the hash, so it must + // be extracted first. + let (_, hash_bytes) = read_digest_info(data)?; + let hash = HCryptHash::new(hcryptprov, hash_bytes)?; + let mut signature_len = 0; + if unsafe { + CryptSignHashW( + *hash, + *key_spec, + std::ptr::null_mut(), + 0, + std::ptr::null_mut(), + &mut signature_len, + ) + } != 1 + { + return Err(error_here!( + ErrorType::ExternalError, + unsafe { GetLastError() }.to_string() + )); + } + let mut signature = vec![0; signature_len as usize]; + if !do_signature { + return Ok(signature); + } + let mut final_signature_len = signature_len; + if unsafe { + CryptSignHashW( + *hash, + *key_spec, + std::ptr::null_mut(), + 0, + signature.as_mut_ptr(), + &mut final_signature_len, + ) + } != 1 + { + return Err(error_here!( + ErrorType::ExternalError, + unsafe { GetLastError() }.to_string() + )); + } + if final_signature_len != signature_len { + return Err(error_here!(ErrorType::ExternalError)); + } + // CryptoAPI returns the signature with the most significant byte last (little-endian), + // whereas PKCS#11 expects the most significant byte first (big-endian). + signature.reverse(); + Ok(signature) +} + +struct HCryptHash(HCRYPTHASH); + +impl HCryptHash { + fn new(hcryptprov: &HCRYPTPROV, hash_bytes: &[u8]) -> Result<HCryptHash, Error> { + let alg = match hash_bytes.len() { + 20 => CALG_SHA1, + 32 => CALG_SHA_256, + 48 => CALG_SHA_384, + 64 => CALG_SHA_512, + _ => { + return Err(error_here!(ErrorType::UnsupportedInput)); + } + }; + let mut hash: HCRYPTHASH = 0; + if unsafe { CryptCreateHash(*hcryptprov, alg, 0, 0, &mut hash) } != 1 { + return Err(error_here!( + ErrorType::ExternalError, + unsafe { GetLastError() }.to_string() + )); + } + if unsafe { CryptSetHashParam(hash, HP_HASHVAL, hash_bytes.as_ptr(), 0) } != 1 { + return Err(error_here!( + ErrorType::ExternalError, + unsafe { GetLastError() }.to_string() + )); + } + Ok(HCryptHash(hash)) + } +} + +impl Drop for HCryptHash { + fn drop(&mut self) { + unsafe { + CryptDestroyHash(self.0); + } + } +} + +impl Deref for HCryptHash { + type Target = HCRYPTHASH; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +// In some cases, the ncrypt API takes a pointer to a null-terminated wide-character string as a way +// of specifying an algorithm. The "right" way to do this would be to take the corresponding +// &'static str constant provided by the winapi crate, create an OsString from it, encode it as wide +// characters, and collect it into a Vec<u16>. However, since the implementation that provides this +// functionality isn't constant, we would have to manage the memory this creates and uses. Since +// rust structures generally can't be self-referrential, this memory would have to live elsewhere, +// and the nice abstractions we've created for this implementation start to break down. It's much +// simpler to hard-code the identifiers we support, since there are only four of them. +// The following arrays represent the identifiers "SHA1", "SHA256", "SHA384", and "SHA512", +// respectively. +const SHA1_ALGORITHM_STRING: &[u16] = &[83, 72, 65, 49, 0]; +const SHA256_ALGORITHM_STRING: &[u16] = &[83, 72, 65, 50, 53, 54, 0]; +const SHA384_ALGORITHM_STRING: &[u16] = &[83, 72, 65, 51, 56, 52, 0]; +const SHA512_ALGORITHM_STRING: &[u16] = &[83, 72, 65, 53, 49, 50, 0]; + +enum SignParams { + EC, + RSA_PKCS1(BCRYPT_PKCS1_PADDING_INFO), + RSA_PSS(BCRYPT_PSS_PADDING_INFO), +} + +impl SignParams { + fn new( + key_type: KeyType, + params: &Option<CK_RSA_PKCS_PSS_PARAMS>, + ) -> Result<SignParams, Error> { + // EC is easy, so handle that first. + match key_type { + KeyType::EC => return Ok(SignParams::EC), + KeyType::RSA => {} + } + // If `params` is `Some`, we're doing RSA-PSS. If it is `None`, we're doing RSA-PKCS1. + let pss_params = match params { + Some(pss_params) => pss_params, + None => { + // The hash algorithm should be encoded in the data to be signed, so we don't have to + // (and don't want to) specify a particular algorithm here. + return Ok(SignParams::RSA_PKCS1(BCRYPT_PKCS1_PADDING_INFO { + pszAlgId: std::ptr::null(), + })); + } + }; + let algorithm_string = match pss_params.hashAlg { + CKM_SHA_1 => SHA1_ALGORITHM_STRING, + CKM_SHA256 => SHA256_ALGORITHM_STRING, + CKM_SHA384 => SHA384_ALGORITHM_STRING, + CKM_SHA512 => SHA512_ALGORITHM_STRING, + _ => { + return Err(error_here!(ErrorType::UnsupportedInput)); + } + }; + Ok(SignParams::RSA_PSS(BCRYPT_PSS_PADDING_INFO { + pszAlgId: algorithm_string.as_ptr(), + cbSalt: pss_params.sLen, + })) + } + + fn params_ptr(&mut self) -> *mut std::ffi::c_void { + match self { + SignParams::EC => std::ptr::null_mut(), + SignParams::RSA_PKCS1(params) => { + params as *mut BCRYPT_PKCS1_PADDING_INFO as *mut std::ffi::c_void + } + SignParams::RSA_PSS(params) => { + params as *mut BCRYPT_PSS_PADDING_INFO as *mut std::ffi::c_void + } + } + } + + fn flags(&self) -> u32 { + match *self { + SignParams::EC => 0, + SignParams::RSA_PKCS1(_) => NCRYPT_PAD_PKCS1_FLAG, + SignParams::RSA_PSS(_) => NCRYPT_PAD_PSS_FLAG, + } + } +} + +/// A helper enum to identify a private key's type. We support EC and RSA. +#[allow(clippy::upper_case_acronyms)] +#[derive(Clone, Copy, Debug)] +pub enum KeyType { + EC, + RSA, +} + +/// Represents a private key for which there exists a corresponding certificate. +pub struct Key { + /// A handle on the OS mechanism that represents the certificate for this key. + cert: CertContext, + /// PKCS #11 object class. Will be `CKO_PRIVATE_KEY`. + class: Vec<u8>, + /// Whether or not this is on a token. Will be `CK_TRUE`. + token: Vec<u8>, + /// An identifier unique to this key. Must be the same as the ID for the certificate. + id: Vec<u8>, + /// Whether or not this key is "private" (can it be exported?). Will be CK_TRUE (it can't be + /// exported). + private: Vec<u8>, + /// PKCS #11 key type. Will be `CKK_EC` for EC, and `CKK_RSA` for RSA. + key_type: Vec<u8>, + /// If this is an RSA key, this is the value of the modulus as an unsigned integer. + modulus: Option<Vec<u8>>, + /// If this is an EC key, this is the DER bytes of the OID identifying the curve the key is on. + ec_params: Option<Vec<u8>>, + /// An enum identifying this key's type. + key_type_enum: KeyType, + /// Which slot this key should be exposed on. + slot_type: SlotType, + /// A handle on the OS mechanism that represents this key. + key_handle: Option<KeyHandle>, +} + +impl Key { + fn new(cert_context: PCCERT_CONTEXT) -> Result<Key, Error> { + let cert = unsafe { *cert_context }; + let cert_der = + unsafe { slice::from_raw_parts(cert.pbCertEncoded, cert.cbCertEncoded as usize) }; + let id = Sha256::digest(cert_der).to_vec(); + let id = id.to_vec(); + let cert_info = unsafe { &*cert.pCertInfo }; + let mut modulus = None; + let mut ec_params = None; + let spki = &cert_info.SubjectPublicKeyInfo; + let algorithm_oid = unsafe { CStr::from_ptr(spki.Algorithm.pszObjId) } + .to_str() + .map_err(|_| error_here!(ErrorType::ExternalError))?; + let (key_type_enum, key_type_attribute) = if algorithm_oid == szOID_RSA_RSA { + if spki.PublicKey.cUnusedBits != 0 { + return Err(error_here!(ErrorType::ExternalError)); + } + let public_key_bytes = unsafe { + std::slice::from_raw_parts(spki.PublicKey.pbData, spki.PublicKey.cbData as usize) + }; + let modulus_value = read_rsa_modulus(public_key_bytes)?; + modulus = Some(modulus_value); + (KeyType::RSA, CKK_RSA) + } else if algorithm_oid == szOID_ECC_PUBLIC_KEY { + let params = &spki.Algorithm.Parameters; + ec_params = Some( + unsafe { std::slice::from_raw_parts(params.pbData, params.cbData as usize) } + .to_vec(), + ); + (KeyType::EC, CKK_EC) + } else { + return Err(error_here!(ErrorType::LibraryFailure)); + }; + let cert = CertContext::new(cert_context); + Ok(Key { + cert, + 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, + slot_type: SlotType::Modern, + 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_with_retry( + &mut self, + data: &[u8], + params: &Option<CK_RSA_PKCS_PSS_PARAMS>, + do_signature: bool, + ) -> Result<Vec<u8>, Error> { + let result = self.sign_internal(data, params, do_signature); + if result.is_ok() { + return result; + } + // Some devices appear to not work well when the key handle is held for too long or if a + // card is inserted/removed while Firefox is running. Try refreshing the key handle. + debug!("sign failed: refreshing key handle"); + let _ = self.key_handle.take(); + self.sign_internal(data, params, do_signature) + } + + /// data: the data to sign + /// do_signature: if true, actually perform the signature. Otherwise, return a `Vec<u8>` of the + /// length the signature would be, if performed. + fn sign_internal( + &mut self, + data: &[u8], + params: &Option<CK_RSA_PKCS_PSS_PARAMS>, + do_signature: bool, + ) -> Result<Vec<u8>, Error> { + // If this key hasn't been used for signing yet, there won't be a cached key handle. Obtain + // and cache it if this is the case. Doing so can cause the underlying implementation to + // show an authentication or pin prompt to the user. Caching the handle can avoid causing + // multiple prompts to be displayed in some cases. + if self.key_handle.is_none() { + let _ = self.key_handle.replace(KeyHandle::from_cert(&self.cert)?); + } + let key = match &self.key_handle { + Some(key) => key, + None => return Err(error_here!(ErrorType::LibraryFailure)), + }; + key.sign(data, params, do_signature, self.key_type_enum) + } +} + +impl CryptokiObject for Key { + fn matches(&self, slot_type: SlotType, attrs: &[(CK_ATTRIBUTE_TYPE, Vec<u8>)]) -> bool { + if slot_type != self.slot_type { + 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> { + match self.sign_with_retry(data, params, false) { + Ok(dummy_signature_bytes) => Ok(dummy_signature_bytes.len()), + Err(e) => Err(e), + } + } + + fn sign( + &mut self, + data: &[u8], + params: &Option<CK_RSA_PKCS_PSS_PARAMS>, + ) -> Result<Vec<u8>, Error> { + self.sign_with_retry(data, params, true) + } +} + +struct CertStore { + handle: HCERTSTORE, +} + +impl Drop for CertStore { + fn drop(&mut self) { + if !self.handle.is_null() { + unsafe { + CertCloseStore(self.handle, 0); + } + } + } +} + +impl Deref for CertStore { + type Target = HCERTSTORE; + + fn deref(&self) -> &Self::Target { + &self.handle + } +} + +impl CertStore { + fn new(handle: HCERTSTORE) -> CertStore { + CertStore { handle } + } +} + +// Given a pointer to a CERT_CHAIN_CONTEXT, enumerates each chain in the context and each element +// in each chain to gather every CERT_CONTEXT pointed to by the CERT_CHAIN_CONTEXT. +// https://docs.microsoft.com/en-us/windows/win32/api/wincrypt/ns-wincrypt-cert_chain_context says +// that the 0th element of the 0th chain will be the end-entity certificate. This certificate (if +// present), will be the 0th element of the returned Vec. +fn gather_cert_contexts(cert_chain_context: *const CERT_CHAIN_CONTEXT) -> Vec<*const CERT_CONTEXT> { + let mut cert_contexts = Vec::new(); + if cert_chain_context.is_null() { + return cert_contexts; + } + let cert_chain_context = unsafe { &*cert_chain_context }; + let cert_chains = unsafe { + std::slice::from_raw_parts( + cert_chain_context.rgpChain, + cert_chain_context.cChain as usize, + ) + }; + for cert_chain in cert_chains { + // First dereference the borrow. + let cert_chain = *cert_chain; + if cert_chain.is_null() { + continue; + } + // Then dereference the pointer. + let cert_chain = unsafe { &*cert_chain }; + let chain_elements = unsafe { + std::slice::from_raw_parts(cert_chain.rgpElement, cert_chain.cElement as usize) + }; + for chain_element in chain_elements { + let chain_element = *chain_element; // dereference borrow + if chain_element.is_null() { + continue; + } + let chain_element = unsafe { &*chain_element }; // dereference pointer + cert_contexts.push(chain_element.pCertContext); + } + } + cert_contexts +} + +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 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, + ) + }; + 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)) + } +} diff --git a/security/manager/ssl/osclientcerts/src/bindings_macos.rs b/security/manager/ssl/osclientcerts/src/bindings_macos.rs new file mode 100644 index 0000000000..7294bbb555 --- /dev/null +++ b/security/manager/ssl/osclientcerts/src/bindings_macos.rs @@ -0,0 +1,79 @@ +/* -*- 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/. */ + +// This was originally generated by rust-bindgen at build time. Later in +// development it became clear that using bindgen for this library as part of +// mozilla-central would be difficult (if not impossible). So, this was +// converted to a static file and unused declarations were removed. Also, +// intermediate types added by rust-bindgen were removed for clarity. + +pub type OSStatus = i32; +pub const errSecSuccess: OSStatus = 0; +pub const errSecItemNotFound: OSStatus = -25300; + +pub type SecKeyAlgorithm = CFStringRef; + +extern "C" { + // Available starting macOS 10.3 + pub fn SecCertificateGetTypeID() -> CFTypeID; + pub fn SecTrustCreateWithCertificates( + certificates: SecCertificateRef, + policies: SecPolicyRef, + trust: *mut SecTrustRef, + ) -> OSStatus; + pub fn SecIdentityGetTypeID() -> CFTypeID; + pub fn SecIdentityCopyCertificate( + identityRef: SecIdentityRef, + certificateRef: *mut SecCertificateRef, + ) -> OSStatus; + pub fn SecIdentityCopyPrivateKey( + identityRef: SecIdentityRef, + privateKeyRef: *mut SecKeyRef, + ) -> OSStatus; + pub fn SecKeyGetTypeID() -> CFTypeID; + pub fn SecPolicyGetTypeID() -> CFTypeID; + pub fn SecTrustGetTypeID() -> CFTypeID; + + // Available starting macOS 10.6 + pub fn SecCertificateCopyData(certificate: SecCertificateRef) -> CFDataRef; + pub fn SecCertificateCopySubjectSummary(certificate: SecCertificateRef) -> CFStringRef; + pub fn SecItemCopyMatching(query: CFDictionaryRef, result: *mut CFTypeRef) -> OSStatus; + pub fn SecPolicyCreateSSL(server: bool, hostname: CFStringRef) -> SecPolicyRef; + pub static kSecClass: CFStringRef; + pub static kSecAttrKeyType: CFStringRef; + pub static kSecAttrKeySizeInBits: CFStringRef; + pub static kSecMatchLimit: CFStringRef; + pub static kSecMatchLimitAll: CFStringRef; + pub static kSecReturnRef: CFStringRef; + + // Available starting macOS 10.7 + pub fn SecTrustGetCertificateAtIndex(trust: SecTrustRef, ix: CFIndex) -> SecCertificateRef; + pub fn SecTrustGetCertificateCount(trust: SecTrustRef) -> CFIndex; + pub static kSecClassIdentity: CFStringRef; + pub static kSecAttrKeyTypeRSA: CFStringRef; + + // Available starting macOS 10.9 + pub fn SecTrustSetNetworkFetchAllowed(trust: SecTrustRef, allowFetch: Boolean) -> OSStatus; + + // Available starting macOS 10.12 + pub fn SecKeyCreateSignature( + key: SecKeyRef, + algorithm: SecKeyAlgorithm, + data: CFDataRef, + err: *mut CFErrorRef, + ) -> CFDataRef; + pub fn SecKeyCopyAttributes(key: SecKeyRef) -> CFDictionaryRef; + pub fn SecKeyCopyExternalRepresentation(key: SecKeyRef, err: *mut CFErrorRef) -> CFDataRef; + pub static kSecKeyAlgorithmECDSASignatureDigestX962SHA1: CFStringRef; + pub static kSecKeyAlgorithmECDSASignatureDigestX962SHA256: CFStringRef; + pub static kSecKeyAlgorithmECDSASignatureDigestX962SHA384: CFStringRef; + pub static kSecKeyAlgorithmECDSASignatureDigestX962SHA512: CFStringRef; + pub static kSecKeyAlgorithmRSASignatureDigestPKCS1v15Raw: CFStringRef; + pub static kSecKeyAlgorithmRSASignatureDigestPKCS1v15SHA256: CFStringRef; + pub static kSecKeyAlgorithmRSASignatureDigestPKCS1v15SHA384: CFStringRef; + pub static kSecKeyAlgorithmRSASignatureDigestPKCS1v15SHA512: CFStringRef; + pub static kSecKeyAlgorithmRSASignatureDigestPKCS1v15SHA1: CFStringRef; + pub static kSecAttrKeyTypeECSECPrimeRandom: CFStringRef; +} diff --git a/security/manager/ssl/osclientcerts/src/lib.rs b/security/manager/ssl/osclientcerts/src/lib.rs new file mode 100644 index 0000000000..556ff34156 --- /dev/null +++ b/security/manager/ssl/osclientcerts/src/lib.rs @@ -0,0 +1,1218 @@ +/* -*- 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_snake_case)] + +extern crate byteorder; +#[cfg(target_os = "macos")] +#[macro_use] +extern crate core_foundation; +extern crate env_logger; +#[macro_use] +extern crate lazy_static; +#[cfg(target_os = "macos")] +extern crate libloading; +#[macro_use] +extern crate log; +extern crate pkcs11_bindings; +#[macro_use] +extern crate rsclientcerts; +extern crate sha2; +#[cfg(target_os = "windows")] +extern crate winapi; + +use pkcs11_bindings::*; +use rsclientcerts::manager::{ManagerProxy, SlotType}; +use std::sync::Mutex; +use std::thread; + +#[cfg(target_os = "macos")] +mod backend_macos; +#[cfg(target_os = "windows")] +mod backend_windows; + +#[cfg(target_os = "macos")] +use crate::backend_macos::Backend; +#[cfg(target_os = "windows")] +use crate::backend_windows::Backend; + +lazy_static! { + /// 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 ref 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 { + () => { + match MANAGER_PROXY.lock() { + Ok(maybe_manager_proxy) => maybe_manager_proxy, + Err(poison_error) => { + log_with_thread_id!( + error, + "previous thread panicked acquiring manager lock: {}", + poison_error + ); + return CKR_DEVICE_ERROR; + } + } + }; +} + +macro_rules! manager_guard_to_manager { + ($manager_guard:ident) => { + match $manager_guard.as_mut() { + Some(manager_proxy) => manager_proxy, + None => { + log_with_thread_id!(error, "manager expected to be set, but it is not"); + return CKR_DEVICE_ERROR; + } + } + }; +} + +// Helper macro to prefix log messages with the current thread ID. +macro_rules! log_with_thread_id { + ($log_level:ident, $($message:expr),*) => { + $log_level!("{:?} {}", thread::current().id(), format_args!($($message),*)); + }; +} + +/// This gets called to initialize the module. For this implementation, this consists of +/// 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. + let _ = env_logger::try_init(); + let mut manager_guard = try_to_get_manager_guard!(); + let manager_proxy = match ManagerProxy::new(Backend {}) { + Ok(p) => p, + Err(e) => { + log_with_thread_id!(error, "C_Initialize: ManagerProxy: {}", e); + return CKR_DEVICE_ERROR; + } + }; + match manager_guard.replace(manager_proxy) { + Some(_unexpected_previous_manager) => { + #[cfg(target_os = "macos")] + { + log_with_thread_id!(info, "C_Initialize: manager previously set (this is expected on macOS - replacing it)"); + } + #[cfg(target_os = "windows")] + { + log_with_thread_id!(warn, "C_Initialize: manager unexpectedly previously set (bravely continuing by replacing it)"); + } + } + None => {} + } + log_with_thread_id!(debug, "C_Initialize: CKR_OK"); + CKR_OK +} + +extern "C" fn C_Finalize(_pReserved: CK_VOID_PTR) -> CK_RV { + let mut manager_guard = try_to_get_manager_guard!(); + let manager = manager_guard_to_manager!(manager_guard); + match manager.stop() { + Ok(()) => { + log_with_thread_id!(debug, "C_Finalize: CKR_OK"); + CKR_OK + } + Err(e) => { + log_with_thread_id!(error, "C_Finalize: CKR_DEVICE_ERROR: {}", e); + CKR_DEVICE_ERROR + } + } +} + +// The specification mandates that these strings be padded with spaces to the appropriate length. +// Since the length of fixed-size arrays in rust is part of the type, the compiler enforces that +// these byte strings are of the correct length. +const MANUFACTURER_ID_BYTES: &[u8; 32] = b"Mozilla Corporation "; +const LIBRARY_DESCRIPTION_BYTES: &[u8; 32] = b"OS Client Cert Module "; + +/// This gets called to gather some information about the module. In particular, this implementation +/// supports (portions of) cryptoki (PKCS #11) version 2.2. +extern "C" fn C_GetInfo(pInfo: CK_INFO_PTR) -> CK_RV { + if pInfo.is_null() { + log_with_thread_id!(error, "C_GetInfo: CKR_ARGUMENTS_BAD"); + return CKR_ARGUMENTS_BAD; + } + log_with_thread_id!(debug, "C_GetInfo: CKR_OK"); + let mut info = CK_INFO::default(); + info.cryptokiVersion.major = 2; + info.cryptokiVersion.minor = 2; + info.manufacturerID = *MANUFACTURER_ID_BYTES; + info.libraryDescription = *LIBRARY_DESCRIPTION_BYTES; + unsafe { + *pInfo = info; + } + CKR_OK +} + +/// This module has two slots. +const SLOT_COUNT: CK_ULONG = 2; +/// The slot with ID 1 supports modern mechanisms like RSA-PSS. +const SLOT_ID_MODERN: CK_SLOT_ID = 1; +/// The slot with ID 2 only supports legacy mechanisms. +const SLOT_ID_LEGACY: CK_SLOT_ID = 2; + +/// This gets called twice: once with a null `pSlotList` to get the number of slots (returned via +/// `pulCount`) and a second time to get the ID for each slot. +extern "C" fn C_GetSlotList( + _tokenPresent: CK_BBOOL, + pSlotList: CK_SLOT_ID_PTR, + pulCount: CK_ULONG_PTR, +) -> CK_RV { + if pulCount.is_null() { + log_with_thread_id!(error, "C_GetSlotList: CKR_ARGUMENTS_BAD"); + return CKR_ARGUMENTS_BAD; + } + if !pSlotList.is_null() { + if unsafe { *pulCount } < SLOT_COUNT { + log_with_thread_id!(error, "C_GetSlotList: CKR_BUFFER_TOO_SMALL"); + return CKR_BUFFER_TOO_SMALL; + } + unsafe { + *pSlotList = SLOT_ID_MODERN; + *pSlotList.offset(1) = SLOT_ID_LEGACY; + } + }; + unsafe { + *pulCount = SLOT_COUNT; + } + log_with_thread_id!(debug, "C_GetSlotList: CKR_OK"); + CKR_OK +} + +const SLOT_DESCRIPTION_MODERN_BYTES: &[u8; 64] = + b"OS Client Cert Slot (Modern) "; +const SLOT_DESCRIPTION_LEGACY_BYTES: &[u8; 64] = + b"OS Client Cert Slot (Legacy) "; + +/// This gets called to obtain information about slots. In this implementation, the tokens are +/// always present in the slots. +extern "C" fn C_GetSlotInfo(slotID: CK_SLOT_ID, pInfo: CK_SLOT_INFO_PTR) -> CK_RV { + if (slotID != SLOT_ID_MODERN && slotID != SLOT_ID_LEGACY) || pInfo.is_null() { + log_with_thread_id!(error, "C_GetSlotInfo: CKR_ARGUMENTS_BAD"); + return CKR_ARGUMENTS_BAD; + } + let description = if slotID == SLOT_ID_MODERN { + SLOT_DESCRIPTION_MODERN_BYTES + } else { + SLOT_DESCRIPTION_LEGACY_BYTES + }; + let slot_info = CK_SLOT_INFO { + slotDescription: *description, + manufacturerID: *MANUFACTURER_ID_BYTES, + flags: CKF_TOKEN_PRESENT, + hardwareVersion: CK_VERSION::default(), + firmwareVersion: CK_VERSION::default(), + }; + unsafe { + *pInfo = slot_info; + } + log_with_thread_id!(debug, "C_GetSlotInfo: CKR_OK"); + CKR_OK +} + +const TOKEN_LABEL_MODERN_BYTES: &[u8; 32] = b"OS Client Cert Token (Modern) "; +const TOKEN_LABEL_LEGACY_BYTES: &[u8; 32] = b"OS Client Cert Token (Legacy) "; +const TOKEN_MODEL_BYTES: &[u8; 16] = b"osclientcerts "; +const TOKEN_SERIAL_NUMBER_BYTES: &[u8; 16] = b"0000000000000000"; + +/// This gets called to obtain some information about tokens. This implementation has two slots, +/// so it has two tokens. This information is primarily for display purposes. +extern "C" fn C_GetTokenInfo(slotID: CK_SLOT_ID, pInfo: CK_TOKEN_INFO_PTR) -> CK_RV { + if (slotID != SLOT_ID_MODERN && slotID != SLOT_ID_LEGACY) || pInfo.is_null() { + log_with_thread_id!(error, "C_GetTokenInfo: CKR_ARGUMENTS_BAD"); + return CKR_ARGUMENTS_BAD; + } + let mut token_info = CK_TOKEN_INFO::default(); + let label = if slotID == SLOT_ID_MODERN { + TOKEN_LABEL_MODERN_BYTES + } else { + TOKEN_LABEL_LEGACY_BYTES + }; + token_info.label = *label; + token_info.manufacturerID = *MANUFACTURER_ID_BYTES; + token_info.model = *TOKEN_MODEL_BYTES; + token_info.serialNumber = *TOKEN_SERIAL_NUMBER_BYTES; + unsafe { + *pInfo = token_info; + } + log_with_thread_id!(debug, "C_GetTokenInfo: CKR_OK"); + CKR_OK +} + +/// This gets called to determine what mechanisms a slot supports. The modern slot supports ECDSA, +/// RSA PKCS, and RSA PSS. The legacy slot only supports RSA PKCS. +extern "C" fn C_GetMechanismList( + slotID: CK_SLOT_ID, + pMechanismList: CK_MECHANISM_TYPE_PTR, + pulCount: CK_ULONG_PTR, +) -> CK_RV { + if (slotID != SLOT_ID_MODERN && slotID != SLOT_ID_LEGACY) || pulCount.is_null() { + log_with_thread_id!(error, "C_GetMechanismList: CKR_ARGUMENTS_BAD"); + return CKR_ARGUMENTS_BAD; + } + let mechanisms = if slotID == SLOT_ID_MODERN { + vec![CKM_ECDSA, CKM_RSA_PKCS, CKM_RSA_PKCS_PSS] + } else { + vec![CKM_RSA_PKCS] + }; + if !pMechanismList.is_null() { + if unsafe { *pulCount as usize } < mechanisms.len() { + log_with_thread_id!(error, "C_GetMechanismList: CKR_ARGUMENTS_BAD"); + return CKR_ARGUMENTS_BAD; + } + for (i, mechanism) in mechanisms.iter().enumerate() { + unsafe { + *pMechanismList.add(i) = *mechanism; + } + } + } + unsafe { + *pulCount = mechanisms.len() as CK_ULONG; + } + log_with_thread_id!(debug, "C_GetMechanismList: CKR_OK"); + CKR_OK +} + +extern "C" fn C_GetMechanismInfo( + _slotID: CK_SLOT_ID, + _type: CK_MECHANISM_TYPE, + _pInfo: CK_MECHANISM_INFO_PTR, +) -> CK_RV { + log_with_thread_id!(error, "C_GetMechanismInfo: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_InitToken( + _slotID: CK_SLOT_ID, + _pPin: CK_UTF8CHAR_PTR, + _ulPinLen: CK_ULONG, + _pLabel: CK_UTF8CHAR_PTR, +) -> CK_RV { + log_with_thread_id!(error, "C_InitToken: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_InitPIN( + _hSession: CK_SESSION_HANDLE, + _pPin: CK_UTF8CHAR_PTR, + _ulPinLen: CK_ULONG, +) -> CK_RV { + log_with_thread_id!(error, "C_InitPIN: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_SetPIN( + _hSession: CK_SESSION_HANDLE, + _pOldPin: CK_UTF8CHAR_PTR, + _ulOldLen: CK_ULONG, + _pNewPin: CK_UTF8CHAR_PTR, + _ulNewLen: CK_ULONG, +) -> CK_RV { + log_with_thread_id!(error, "C_SetPIN: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +/// 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, + _pApplication: CK_VOID_PTR, + _Notify: CK_NOTIFY, + phSession: CK_SESSION_HANDLE_PTR, +) -> CK_RV { + if (slotID != SLOT_ID_MODERN && slotID != SLOT_ID_LEGACY) || phSession.is_null() { + 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 slot_type = if slotID == SLOT_ID_MODERN { + SlotType::Modern + } else { + SlotType::Legacy + }; + let session_handle = match manager.open_session(slot_type) { + Ok(session_handle) => session_handle, + Err(e) => { + log_with_thread_id!(error, "C_OpenSession: open_session failed: {}", e); + return CKR_DEVICE_ERROR; + } + }; + unsafe { + *phSession = session_handle; + } + log_with_thread_id!(debug, "C_OpenSession: CKR_OK"); + CKR_OK +} + +/// 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); + if manager.close_session(hSession).is_err() { + log_with_thread_id!(error, "C_CloseSession: CKR_SESSION_HANDLE_INVALID"); + return CKR_SESSION_HANDLE_INVALID; + } + log_with_thread_id!(debug, "C_CloseSession: CKR_OK"); + CKR_OK +} + +/// 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_MODERN && slotID != SLOT_ID_LEGACY { + 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 slot_type = if slotID == SLOT_ID_MODERN { + SlotType::Modern + } else { + SlotType::Legacy + }; + match manager.close_all_sessions(slot_type) { + Ok(()) => { + log_with_thread_id!(debug, "C_CloseAllSessions: CKR_OK"); + CKR_OK + } + Err(e) => { + log_with_thread_id!( + error, + "C_CloseAllSessions: close_all_sessions failed: {}", + e + ); + CKR_DEVICE_ERROR + } + } +} + +extern "C" fn C_GetSessionInfo(_hSession: CK_SESSION_HANDLE, _pInfo: CK_SESSION_INFO_PTR) -> CK_RV { + log_with_thread_id!(error, "C_GetSessionInfo: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_GetOperationState( + _hSession: CK_SESSION_HANDLE, + _pOperationState: CK_BYTE_PTR, + _pulOperationStateLen: CK_ULONG_PTR, +) -> CK_RV { + log_with_thread_id!(error, "C_GetOperationState: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_SetOperationState( + _hSession: CK_SESSION_HANDLE, + _pOperationState: CK_BYTE_PTR, + _ulOperationStateLen: CK_ULONG, + _hEncryptionKey: CK_OBJECT_HANDLE, + _hAuthenticationKey: CK_OBJECT_HANDLE, +) -> CK_RV { + log_with_thread_id!(error, "C_SetOperationState: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_Login( + _hSession: CK_SESSION_HANDLE, + _userType: CK_USER_TYPE, + _pPin: CK_UTF8CHAR_PTR, + _ulPinLen: CK_ULONG, +) -> CK_RV { + log_with_thread_id!(error, "C_Login: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +/// This gets called to log out and drop any authenticated resources. Because this module does not +/// hold on to authenticated resources, this module "implements" this by doing nothing and +/// returning a success result. +extern "C" fn C_Logout(_hSession: CK_SESSION_HANDLE) -> CK_RV { + log_with_thread_id!(debug, "C_Logout: CKR_OK"); + CKR_OK +} + +extern "C" fn C_CreateObject( + _hSession: CK_SESSION_HANDLE, + _pTemplate: CK_ATTRIBUTE_PTR, + _ulCount: CK_ULONG, + _phObject: CK_OBJECT_HANDLE_PTR, +) -> CK_RV { + log_with_thread_id!(error, "C_CreateObject: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_CopyObject( + _hSession: CK_SESSION_HANDLE, + _hObject: CK_OBJECT_HANDLE, + _pTemplate: CK_ATTRIBUTE_PTR, + _ulCount: CK_ULONG, + _phNewObject: CK_OBJECT_HANDLE_PTR, +) -> CK_RV { + log_with_thread_id!(error, "C_CopyObject: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_DestroyObject(_hSession: CK_SESSION_HANDLE, _hObject: CK_OBJECT_HANDLE) -> CK_RV { + log_with_thread_id!(error, "C_DestroyObject: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_GetObjectSize( + _hSession: CK_SESSION_HANDLE, + _hObject: CK_OBJECT_HANDLE, + _pulSize: CK_ULONG_PTR, +) -> CK_RV { + log_with_thread_id!(error, "C_GetObjectSize: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +/// This gets called to obtain the values of a number of attributes of an object identified by the +/// given handle. This module implements this by requesting that the `ManagerProxy` find the object +/// 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 +/// values. +extern "C" fn C_GetAttributeValue( + _hSession: CK_SESSION_HANDLE, + hObject: CK_OBJECT_HANDLE, + pTemplate: CK_ATTRIBUTE_PTR, + ulCount: CK_ULONG, +) -> CK_RV { + if pTemplate.is_null() { + log_with_thread_id!(error, "C_GetAttributeValue: CKR_ARGUMENTS_BAD"); + return CKR_ARGUMENTS_BAD; + } + let mut attr_types = Vec::with_capacity(ulCount as usize); + for i in 0..ulCount as usize { + 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 values = match manager.get_attributes(hObject, attr_types) { + Ok(values) => values, + Err(e) => { + log_with_thread_id!(error, "C_GetAttributeValue: CKR_ARGUMENTS_BAD ({})", e); + return CKR_ARGUMENTS_BAD; + } + }; + if values.len() != ulCount as usize { + log_with_thread_id!( + error, + "C_GetAttributeValue: manager.get_attributes didn't return the right number of values" + ); + return CKR_DEVICE_ERROR; + } + for (i, value) in values.iter().enumerate().take(ulCount as usize) { + let mut attr = unsafe { &mut *pTemplate.add(i) }; + if let Some(attr_value) = value { + if attr.pValue.is_null() { + attr.ulValueLen = attr_value.len() as CK_ULONG; + } else { + let ptr: *mut u8 = attr.pValue as *mut u8; + if attr_value.len() != attr.ulValueLen as usize { + log_with_thread_id!(error, "C_GetAttributeValue: incorrect attr size"); + return CKR_ARGUMENTS_BAD; + } + unsafe { + std::ptr::copy_nonoverlapping(attr_value.as_ptr(), ptr, attr_value.len()); + } + } + } else { + attr.ulValueLen = (0 - 1) as CK_ULONG; + } + } + log_with_thread_id!(debug, "C_GetAttributeValue: CKR_OK"); + CKR_OK +} + +extern "C" fn C_SetAttributeValue( + _hSession: CK_SESSION_HANDLE, + _hObject: CK_OBJECT_HANDLE, + _pTemplate: CK_ATTRIBUTE_PTR, + _ulCount: CK_ULONG, +) -> CK_RV { + log_with_thread_id!(error, "C_SetAttributeValue: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +fn trace_attr(prefix: &str, attr: &CK_ATTRIBUTE) { + let typ = match unsafe_packed_field_access!(attr.type_) { + CKA_CLASS => "CKA_CLASS".to_string(), + CKA_TOKEN => "CKA_TOKEN".to_string(), + CKA_LABEL => "CKA_LABEL".to_string(), + CKA_ID => "CKA_ID".to_string(), + CKA_VALUE => "CKA_VALUE".to_string(), + CKA_ISSUER => "CKA_ISSUER".to_string(), + CKA_SERIAL_NUMBER => "CKA_SERIAL_NUMBER".to_string(), + CKA_SUBJECT => "CKA_SUBJECT".to_string(), + CKA_PRIVATE => "CKA_PRIVATE".to_string(), + CKA_KEY_TYPE => "CKA_KEY_TYPE".to_string(), + CKA_MODULUS => "CKA_MODULUS".to_string(), + CKA_EC_PARAMS => "CKA_EC_PARAMS".to_string(), + _ => format!("0x{:x}", unsafe_packed_field_access!(attr.type_)), + }; + let value = + unsafe { std::slice::from_raw_parts(attr.pValue as *const u8, attr.ulValueLen as usize) }; + log_with_thread_id!( + trace, + "{}CK_ATTRIBUTE {{ type: {}, pValue: {:?}, ulValueLen: {} }}", + prefix, + typ, + value, + unsafe_packed_field_access!(attr.ulValueLen) + ); +} + +/// This gets called to initialize a search for objects matching a given list of attributes. This +/// module implements this by gathering the attributes and passing them to the `ManagerProxy` to +/// start the search. +extern "C" fn C_FindObjectsInit( + hSession: CK_SESSION_HANDLE, + pTemplate: CK_ATTRIBUTE_PTR, + ulCount: CK_ULONG, +) -> CK_RV { + if pTemplate.is_null() { + log_with_thread_id!(error, "C_FindObjectsInit: CKR_ARGUMENTS_BAD"); + return CKR_ARGUMENTS_BAD; + } + let mut attrs = Vec::new(); + log_with_thread_id!(trace, "C_FindObjectsInit:"); + for i in 0..ulCount as usize { + let attr = unsafe { &*pTemplate.add(i) }; + trace_attr(" ", attr); + let slice = unsafe { + std::slice::from_raw_parts(attr.pValue as *const u8, attr.ulValueLen as usize) + }; + attrs.push((attr.type_, slice.to_owned())); + } + let mut manager_guard = try_to_get_manager_guard!(); + let manager = manager_guard_to_manager!(manager_guard); + match manager.start_search(hSession, attrs) { + Ok(()) => {} + Err(e) => { + log_with_thread_id!(error, "C_FindObjectsInit: CKR_ARGUMENTS_BAD: {}", e); + return CKR_ARGUMENTS_BAD; + } + } + log_with_thread_id!(debug, "C_FindObjectsInit: CKR_OK"); + CKR_OK +} + +/// This gets called after `C_FindObjectsInit` to get the results of a search. This module +/// implements this by looking up the search in the `ManagerProxy` and copying out the matching +/// object handles. +extern "C" fn C_FindObjects( + hSession: CK_SESSION_HANDLE, + phObject: CK_OBJECT_HANDLE_PTR, + ulMaxObjectCount: CK_ULONG, + pulObjectCount: CK_ULONG_PTR, +) -> CK_RV { + if phObject.is_null() || pulObjectCount.is_null() || ulMaxObjectCount == 0 { + 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 handles = match manager.search(hSession, ulMaxObjectCount as usize) { + Ok(handles) => handles, + Err(e) => { + log_with_thread_id!(error, "C_FindObjects: CKR_ARGUMENTS_BAD: {}", e); + return CKR_ARGUMENTS_BAD; + } + }; + log_with_thread_id!(debug, "C_FindObjects: found handles {:?}", handles); + if handles.len() > ulMaxObjectCount as usize { + log_with_thread_id!(error, "C_FindObjects: manager returned too many handles"); + return CKR_DEVICE_ERROR; + } + unsafe { + *pulObjectCount = handles.len() as CK_ULONG; + } + for (index, handle) in handles.iter().enumerate() { + if index < ulMaxObjectCount as usize { + unsafe { + *(phObject.add(index)) = *handle; + } + } + } + log_with_thread_id!(debug, "C_FindObjects: CKR_OK"); + CKR_OK +} + +/// This gets called after `C_FindObjectsInit` and `C_FindObjects` to finish a search. The module +/// 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); + // 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(()) => { + log_with_thread_id!(debug, "C_FindObjectsFinal: CKR_OK"); + CKR_OK + } + Err(e) => { + log_with_thread_id!(error, "C_FindObjectsFinal: clear_search failed: {}", e); + CKR_DEVICE_ERROR + } + } +} + +extern "C" fn C_EncryptInit( + _hSession: CK_SESSION_HANDLE, + _pMechanism: CK_MECHANISM_PTR, + _hKey: CK_OBJECT_HANDLE, +) -> CK_RV { + log_with_thread_id!(error, "C_EncryptInit: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_Encrypt( + _hSession: CK_SESSION_HANDLE, + _pData: CK_BYTE_PTR, + _ulDataLen: CK_ULONG, + _pEncryptedData: CK_BYTE_PTR, + _pulEncryptedDataLen: CK_ULONG_PTR, +) -> CK_RV { + log_with_thread_id!(error, "C_Encrypt: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_EncryptUpdate( + _hSession: CK_SESSION_HANDLE, + _pPart: CK_BYTE_PTR, + _ulPartLen: CK_ULONG, + _pEncryptedPart: CK_BYTE_PTR, + _pulEncryptedPartLen: CK_ULONG_PTR, +) -> CK_RV { + log_with_thread_id!(error, "C_EncryptUpdate: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_EncryptFinal( + _hSession: CK_SESSION_HANDLE, + _pLastEncryptedPart: CK_BYTE_PTR, + _pulLastEncryptedPartLen: CK_ULONG_PTR, +) -> CK_RV { + log_with_thread_id!(error, "C_EncryptFinal: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_DecryptInit( + _hSession: CK_SESSION_HANDLE, + _pMechanism: CK_MECHANISM_PTR, + _hKey: CK_OBJECT_HANDLE, +) -> CK_RV { + log_with_thread_id!(error, "C_DecryptInit: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_Decrypt( + _hSession: CK_SESSION_HANDLE, + _pEncryptedData: CK_BYTE_PTR, + _ulEncryptedDataLen: CK_ULONG, + _pData: CK_BYTE_PTR, + _pulDataLen: CK_ULONG_PTR, +) -> CK_RV { + log_with_thread_id!(error, "C_Decrypt: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_DecryptUpdate( + _hSession: CK_SESSION_HANDLE, + _pEncryptedPart: CK_BYTE_PTR, + _ulEncryptedPartLen: CK_ULONG, + _pPart: CK_BYTE_PTR, + _pulPartLen: CK_ULONG_PTR, +) -> CK_RV { + log_with_thread_id!(error, "C_DecryptUpdate: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_DecryptFinal( + _hSession: CK_SESSION_HANDLE, + _pLastPart: CK_BYTE_PTR, + _pulLastPartLen: CK_ULONG_PTR, +) -> CK_RV { + log_with_thread_id!(error, "C_DecryptFinal: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_DigestInit(_hSession: CK_SESSION_HANDLE, _pMechanism: CK_MECHANISM_PTR) -> CK_RV { + log_with_thread_id!(error, "C_DigestInit: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_Digest( + _hSession: CK_SESSION_HANDLE, + _pData: CK_BYTE_PTR, + _ulDataLen: CK_ULONG, + _pDigest: CK_BYTE_PTR, + _pulDigestLen: CK_ULONG_PTR, +) -> CK_RV { + log_with_thread_id!(error, "C_Digest: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_DigestUpdate( + _hSession: CK_SESSION_HANDLE, + _pPart: CK_BYTE_PTR, + _ulPartLen: CK_ULONG, +) -> CK_RV { + log_with_thread_id!(error, "C_DigestUpdate: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_DigestKey(_hSession: CK_SESSION_HANDLE, _hKey: CK_OBJECT_HANDLE) -> CK_RV { + log_with_thread_id!(error, "C_DigestKey: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_DigestFinal( + _hSession: CK_SESSION_HANDLE, + _pDigest: CK_BYTE_PTR, + _pulDigestLen: CK_ULONG_PTR, +) -> CK_RV { + log_with_thread_id!(error, "C_DigestFinal: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +/// 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, + hKey: CK_OBJECT_HANDLE, +) -> CK_RV { + if pMechanism.is_null() { + log_with_thread_id!(error, "C_SignInit: CKR_ARGUMENTS_BAD"); + return CKR_ARGUMENTS_BAD; + } + // Presumably we should validate the mechanism against hKey, but the specification doesn't + // actually seem to require this. + let mechanism = unsafe { *pMechanism }; + log_with_thread_id!(debug, "C_SignInit: mechanism is {:?}", mechanism); + let mechanism_params = if mechanism.mechanism == CKM_RSA_PKCS_PSS { + if mechanism.ulParameterLen as usize != std::mem::size_of::<CK_RSA_PKCS_PSS_PARAMS>() { + log_with_thread_id!( + error, + "C_SignInit: bad ulParameterLen for CKM_RSA_PKCS_PSS: {}", + unsafe_packed_field_access!(mechanism.ulParameterLen) + ); + return CKR_ARGUMENTS_BAD; + } + Some(unsafe { *(mechanism.pParameter as *const CK_RSA_PKCS_PSS_PARAMS) }) + } else { + None + }; + let mut manager_guard = try_to_get_manager_guard!(); + let manager = manager_guard_to_manager!(manager_guard); + match manager.start_sign(hSession, hKey, mechanism_params) { + Ok(()) => {} + Err(e) => { + log_with_thread_id!(error, "C_SignInit: CKR_GENERAL_ERROR: {}", e); + return CKR_GENERAL_ERROR; + } + }; + log_with_thread_id!(debug, "C_SignInit: CKR_OK"); + CKR_OK +} + +/// NSS calls this after `C_SignInit` (there are more ways in the PKCS #11 specification to sign +/// data, but this is the only way supported by this module). The module essentially defers to the +/// `ManagerProxy` and copies out the resulting signature. +extern "C" fn C_Sign( + hSession: CK_SESSION_HANDLE, + pData: CK_BYTE_PTR, + ulDataLen: CK_ULONG, + pSignature: CK_BYTE_PTR, + pulSignatureLen: CK_ULONG_PTR, +) -> CK_RV { + if pData.is_null() || pulSignatureLen.is_null() { + log_with_thread_id!(error, "C_Sign: CKR_ARGUMENTS_BAD"); + return CKR_ARGUMENTS_BAD; + } + 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); + match manager.get_signature_length(hSession, data.to_vec()) { + Ok(signature_length) => unsafe { + *pulSignatureLen = signature_length as CK_ULONG; + }, + Err(e) => { + log_with_thread_id!(error, "C_Sign: get_signature_length failed: {}", e); + return CKR_GENERAL_ERROR; + } + } + } else { + let mut manager_guard = try_to_get_manager_guard!(); + let manager = manager_guard_to_manager!(manager_guard); + match manager.sign(hSession, data.to_vec()) { + Ok(signature) => { + let signature_capacity = unsafe { *pulSignatureLen } as usize; + if signature_capacity < signature.len() { + log_with_thread_id!(error, "C_Sign: CKR_ARGUMENTS_BAD"); + return CKR_ARGUMENTS_BAD; + } + let ptr: *mut u8 = pSignature as *mut u8; + unsafe { + std::ptr::copy_nonoverlapping(signature.as_ptr(), ptr, signature.len()); + *pulSignatureLen = signature.len() as CK_ULONG; + } + } + Err(e) => { + log_with_thread_id!(error, "C_Sign: sign failed: {}", e); + return CKR_GENERAL_ERROR; + } + } + } + log_with_thread_id!(debug, "C_Sign: CKR_OK"); + CKR_OK +} + +extern "C" fn C_SignUpdate( + _hSession: CK_SESSION_HANDLE, + _pPart: CK_BYTE_PTR, + _ulPartLen: CK_ULONG, +) -> CK_RV { + log_with_thread_id!(error, "C_SignUpdate: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_SignFinal( + _hSession: CK_SESSION_HANDLE, + _pSignature: CK_BYTE_PTR, + _pulSignatureLen: CK_ULONG_PTR, +) -> CK_RV { + log_with_thread_id!(error, "C_SignFinal: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_SignRecoverInit( + _hSession: CK_SESSION_HANDLE, + _pMechanism: CK_MECHANISM_PTR, + _hKey: CK_OBJECT_HANDLE, +) -> CK_RV { + log_with_thread_id!(error, "C_SignRecoverInit: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_SignRecover( + _hSession: CK_SESSION_HANDLE, + _pData: CK_BYTE_PTR, + _ulDataLen: CK_ULONG, + _pSignature: CK_BYTE_PTR, + _pulSignatureLen: CK_ULONG_PTR, +) -> CK_RV { + log_with_thread_id!(error, "C_SignRecover: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_VerifyInit( + _hSession: CK_SESSION_HANDLE, + _pMechanism: CK_MECHANISM_PTR, + _hKey: CK_OBJECT_HANDLE, +) -> CK_RV { + log_with_thread_id!(error, "C_VerifyInit: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_Verify( + _hSession: CK_SESSION_HANDLE, + _pData: CK_BYTE_PTR, + _ulDataLen: CK_ULONG, + _pSignature: CK_BYTE_PTR, + _ulSignatureLen: CK_ULONG, +) -> CK_RV { + log_with_thread_id!(error, "C_Verify: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_VerifyUpdate( + _hSession: CK_SESSION_HANDLE, + _pPart: CK_BYTE_PTR, + _ulPartLen: CK_ULONG, +) -> CK_RV { + log_with_thread_id!(error, "C_VerifyUpdate: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_VerifyFinal( + _hSession: CK_SESSION_HANDLE, + _pSignature: CK_BYTE_PTR, + _ulSignatureLen: CK_ULONG, +) -> CK_RV { + log_with_thread_id!(error, "C_VerifyFinal: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_VerifyRecoverInit( + _hSession: CK_SESSION_HANDLE, + _pMechanism: CK_MECHANISM_PTR, + _hKey: CK_OBJECT_HANDLE, +) -> CK_RV { + log_with_thread_id!(error, "C_VerifyRecoverInit: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_VerifyRecover( + _hSession: CK_SESSION_HANDLE, + _pSignature: CK_BYTE_PTR, + _ulSignatureLen: CK_ULONG, + _pData: CK_BYTE_PTR, + _pulDataLen: CK_ULONG_PTR, +) -> CK_RV { + log_with_thread_id!(error, "C_VerifyRecover: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_DigestEncryptUpdate( + _hSession: CK_SESSION_HANDLE, + _pPart: CK_BYTE_PTR, + _ulPartLen: CK_ULONG, + _pEncryptedPart: CK_BYTE_PTR, + _pulEncryptedPartLen: CK_ULONG_PTR, +) -> CK_RV { + log_with_thread_id!(error, "C_DigestEncryptUpdate: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_DecryptDigestUpdate( + _hSession: CK_SESSION_HANDLE, + _pEncryptedPart: CK_BYTE_PTR, + _ulEncryptedPartLen: CK_ULONG, + _pPart: CK_BYTE_PTR, + _pulPartLen: CK_ULONG_PTR, +) -> CK_RV { + log_with_thread_id!(error, "C_DecryptDigestUpdate: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_SignEncryptUpdate( + _hSession: CK_SESSION_HANDLE, + _pPart: CK_BYTE_PTR, + _ulPartLen: CK_ULONG, + _pEncryptedPart: CK_BYTE_PTR, + _pulEncryptedPartLen: CK_ULONG_PTR, +) -> CK_RV { + log_with_thread_id!(error, "C_SignEncryptUpdate: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_DecryptVerifyUpdate( + _hSession: CK_SESSION_HANDLE, + _pEncryptedPart: CK_BYTE_PTR, + _ulEncryptedPartLen: CK_ULONG, + _pPart: CK_BYTE_PTR, + _pulPartLen: CK_ULONG_PTR, +) -> CK_RV { + log_with_thread_id!(error, "C_DecryptVerifyUpdate: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_GenerateKey( + _hSession: CK_SESSION_HANDLE, + _pMechanism: CK_MECHANISM_PTR, + _pTemplate: CK_ATTRIBUTE_PTR, + _ulCount: CK_ULONG, + _phKey: CK_OBJECT_HANDLE_PTR, +) -> CK_RV { + log_with_thread_id!(error, "C_GenerateKey: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_GenerateKeyPair( + _hSession: CK_SESSION_HANDLE, + _pMechanism: CK_MECHANISM_PTR, + _pPublicKeyTemplate: CK_ATTRIBUTE_PTR, + _ulPublicKeyAttributeCount: CK_ULONG, + _pPrivateKeyTemplate: CK_ATTRIBUTE_PTR, + _ulPrivateKeyAttributeCount: CK_ULONG, + _phPublicKey: CK_OBJECT_HANDLE_PTR, + _phPrivateKey: CK_OBJECT_HANDLE_PTR, +) -> CK_RV { + log_with_thread_id!(error, "C_GenerateKeyPair: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_WrapKey( + _hSession: CK_SESSION_HANDLE, + _pMechanism: CK_MECHANISM_PTR, + _hWrappingKey: CK_OBJECT_HANDLE, + _hKey: CK_OBJECT_HANDLE, + _pWrappedKey: CK_BYTE_PTR, + _pulWrappedKeyLen: CK_ULONG_PTR, +) -> CK_RV { + log_with_thread_id!(error, "C_WrapKey: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_UnwrapKey( + _hSession: CK_SESSION_HANDLE, + _pMechanism: CK_MECHANISM_PTR, + _hUnwrappingKey: CK_OBJECT_HANDLE, + _pWrappedKey: CK_BYTE_PTR, + _ulWrappedKeyLen: CK_ULONG, + _pTemplate: CK_ATTRIBUTE_PTR, + _ulAttributeCount: CK_ULONG, + _phKey: CK_OBJECT_HANDLE_PTR, +) -> CK_RV { + log_with_thread_id!(error, "C_UnwrapKey: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_DeriveKey( + _hSession: CK_SESSION_HANDLE, + _pMechanism: CK_MECHANISM_PTR, + _hBaseKey: CK_OBJECT_HANDLE, + _pTemplate: CK_ATTRIBUTE_PTR, + _ulAttributeCount: CK_ULONG, + _phKey: CK_OBJECT_HANDLE_PTR, +) -> CK_RV { + log_with_thread_id!(error, "C_DeriveKey: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_SeedRandom( + _hSession: CK_SESSION_HANDLE, + _pSeed: CK_BYTE_PTR, + _ulSeedLen: CK_ULONG, +) -> CK_RV { + log_with_thread_id!(error, "C_SeedRandom: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_GenerateRandom( + _hSession: CK_SESSION_HANDLE, + _RandomData: CK_BYTE_PTR, + _ulRandomLen: CK_ULONG, +) -> CK_RV { + log_with_thread_id!(error, "C_GenerateRandom: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_GetFunctionStatus(_hSession: CK_SESSION_HANDLE) -> CK_RV { + log_with_thread_id!(error, "C_GetFunctionStatus: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_CancelFunction(_hSession: CK_SESSION_HANDLE) -> CK_RV { + log_with_thread_id!(error, "C_CancelFunction: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_WaitForSlotEvent( + _flags: CK_FLAGS, + _pSlot: CK_SLOT_ID_PTR, + _pRserved: CK_VOID_PTR, +) -> CK_RV { + log_with_thread_id!(error, "C_WaitForSlotEvent: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +/// To be a valid PKCS #11 module, this list of functions must be supported. At least cryptoki 2.2 +/// must be supported for this module to work in NSS. +static mut FUNCTION_LIST: CK_FUNCTION_LIST = CK_FUNCTION_LIST { + version: CK_VERSION { major: 2, minor: 2 }, + C_Initialize: Some(C_Initialize), + C_Finalize: Some(C_Finalize), + C_GetInfo: Some(C_GetInfo), + C_GetFunctionList: None, + C_GetSlotList: Some(C_GetSlotList), + C_GetSlotInfo: Some(C_GetSlotInfo), + C_GetTokenInfo: Some(C_GetTokenInfo), + C_GetMechanismList: Some(C_GetMechanismList), + C_GetMechanismInfo: Some(C_GetMechanismInfo), + C_InitToken: Some(C_InitToken), + C_InitPIN: Some(C_InitPIN), + C_SetPIN: Some(C_SetPIN), + C_OpenSession: Some(C_OpenSession), + C_CloseSession: Some(C_CloseSession), + C_CloseAllSessions: Some(C_CloseAllSessions), + C_GetSessionInfo: Some(C_GetSessionInfo), + C_GetOperationState: Some(C_GetOperationState), + C_SetOperationState: Some(C_SetOperationState), + C_Login: Some(C_Login), + C_Logout: Some(C_Logout), + C_CreateObject: Some(C_CreateObject), + C_CopyObject: Some(C_CopyObject), + C_DestroyObject: Some(C_DestroyObject), + C_GetObjectSize: Some(C_GetObjectSize), + C_GetAttributeValue: Some(C_GetAttributeValue), + C_SetAttributeValue: Some(C_SetAttributeValue), + C_FindObjectsInit: Some(C_FindObjectsInit), + C_FindObjects: Some(C_FindObjects), + C_FindObjectsFinal: Some(C_FindObjectsFinal), + C_EncryptInit: Some(C_EncryptInit), + C_Encrypt: Some(C_Encrypt), + C_EncryptUpdate: Some(C_EncryptUpdate), + C_EncryptFinal: Some(C_EncryptFinal), + C_DecryptInit: Some(C_DecryptInit), + C_Decrypt: Some(C_Decrypt), + C_DecryptUpdate: Some(C_DecryptUpdate), + C_DecryptFinal: Some(C_DecryptFinal), + C_DigestInit: Some(C_DigestInit), + C_Digest: Some(C_Digest), + C_DigestUpdate: Some(C_DigestUpdate), + C_DigestKey: Some(C_DigestKey), + C_DigestFinal: Some(C_DigestFinal), + C_SignInit: Some(C_SignInit), + C_Sign: Some(C_Sign), + C_SignUpdate: Some(C_SignUpdate), + C_SignFinal: Some(C_SignFinal), + C_SignRecoverInit: Some(C_SignRecoverInit), + C_SignRecover: Some(C_SignRecover), + C_VerifyInit: Some(C_VerifyInit), + C_Verify: Some(C_Verify), + C_VerifyUpdate: Some(C_VerifyUpdate), + C_VerifyFinal: Some(C_VerifyFinal), + C_VerifyRecoverInit: Some(C_VerifyRecoverInit), + C_VerifyRecover: Some(C_VerifyRecover), + C_DigestEncryptUpdate: Some(C_DigestEncryptUpdate), + C_DecryptDigestUpdate: Some(C_DecryptDigestUpdate), + C_SignEncryptUpdate: Some(C_SignEncryptUpdate), + C_DecryptVerifyUpdate: Some(C_DecryptVerifyUpdate), + C_GenerateKey: Some(C_GenerateKey), + C_GenerateKeyPair: Some(C_GenerateKeyPair), + C_WrapKey: Some(C_WrapKey), + C_UnwrapKey: Some(C_UnwrapKey), + C_DeriveKey: Some(C_DeriveKey), + C_SeedRandom: Some(C_SeedRandom), + C_GenerateRandom: Some(C_GenerateRandom), + C_GetFunctionStatus: Some(C_GetFunctionStatus), + C_CancelFunction: Some(C_CancelFunction), + C_WaitForSlotEvent: Some(C_WaitForSlotEvent), +}; + +/// # Safety +/// +/// This is the only function this module exposes. NSS calls it to obtain the list of functions +/// comprising this module. +/// ppFunctionList must be a valid pointer. +#[no_mangle] +pub unsafe extern "C" fn C_GetFunctionList(ppFunctionList: CK_FUNCTION_LIST_PTR_PTR) -> CK_RV { + if ppFunctionList.is_null() { + return CKR_ARGUMENTS_BAD; + } + *ppFunctionList = &mut FUNCTION_LIST; + CKR_OK +} + +#[cfg_attr(target_os = "macos", link(name = "Security", kind = "framework"))] +extern "C" {} |