summaryrefslogtreecommitdiffstats
path: root/security/manager/ssl/osclientcerts/src
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
commit2aa4a82499d4becd2284cdb482213d541b8804dd (patch)
treeb80bf8bf13c3766139fbacc530efd0dd9d54394c /security/manager/ssl/osclientcerts/src
parentInitial commit. (diff)
downloadfirefox-2aa4a82499d4becd2284cdb482213d541b8804dd.tar.xz
firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.zip
Adding upstream version 86.0.1.upstream/86.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.rs967
-rw-r--r--security/manager/ssl/osclientcerts/src/backend_windows.rs933
-rw-r--r--security/manager/ssl/osclientcerts/src/bindings_macos.rs58
-rw-r--r--security/manager/ssl/osclientcerts/src/lib.rs1208
-rw-r--r--security/manager/ssl/osclientcerts/src/manager.rs614
-rw-r--r--security/manager/ssl/osclientcerts/src/util.rs463
6 files changed, 4243 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..9d1f1a6b72
--- /dev/null
+++ b/security/manager/ssl/osclientcerts/src/backend_macos.rs
@@ -0,0 +1,967 @@
+/* -*- 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 libloading::{Library, Symbol};
+use pkcs11::types::*;
+use sha2::{Digest, Sha256};
+use std::collections::BTreeMap;
+use std::convert::TryInto;
+use std::os::raw::c_void;
+
+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::*;
+
+// 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");
+
+use crate::manager::SlotType;
+use crate::util::*;
+
+#[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 SecKeyCreateSignatureType =
+ unsafe extern "C" fn(SecKeyRef, SecKeyAlgorithm, CFDataRef, *mut CFErrorRef) -> CFDataRef;
+type SecKeyCopyAttributesType = unsafe extern "C" fn(SecKeyRef) -> CFDictionaryRef;
+type SecKeyCopyExternalRepresentationType =
+ unsafe extern "C" fn(SecKeyRef, *mut CFErrorRef) -> CFDataRef;
+type SecCertificateCopyNormalizedIssuerSequenceType =
+ unsafe extern "C" fn(SecCertificateRef) -> CFDataRef;
+type SecCertificateCopyNormalizedSubjectSequenceType =
+ unsafe extern "C" fn(SecCertificateRef) -> CFDataRef;
+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.12
+ SecKeyAlgorithmECDSASignatureDigestX962SHA1,
+ SecKeyAlgorithmECDSASignatureDigestX962SHA256,
+ SecKeyAlgorithmECDSASignatureDigestX962SHA384,
+ SecKeyAlgorithmECDSASignatureDigestX962SHA512,
+ SecKeyAlgorithmRSASignatureDigestPKCS1v15Raw,
+ SecAttrKeyTypeECSECPrimeRandom,
+ // These are available in macOS 10.13
+ SecKeyAlgorithmRSASignatureDigestPSSSHA1,
+ SecKeyAlgorithmRSASignatureDigestPSSSHA256,
+ SecKeyAlgorithmRSASignatureDigestPSSSHA384,
+ SecKeyAlgorithmRSASignatureDigestPSSSHA512,
+}
+
+// NB: This is not meant to be used outside of this module. It has to be made public because
+// `RentedSecurityFramework` must be public in the rental declaration.
+pub struct SecurityFrameworkFunctions<'a> {
+ sec_key_create_signature: Symbol<'a, SecKeyCreateSignatureType>,
+ sec_key_copy_attributes: Symbol<'a, SecKeyCopyAttributesType>,
+ sec_key_copy_external_representation: Symbol<'a, SecKeyCopyExternalRepresentationType>,
+ sec_certificate_copy_normalized_issuer_sequence:
+ Symbol<'a, SecCertificateCopyNormalizedIssuerSequenceType>,
+ sec_certificate_copy_normalized_subject_sequence:
+ Symbol<'a, SecCertificateCopyNormalizedSubjectSequenceType>,
+ sec_certificate_copy_key: Symbol<'a, SecCertificateCopyKeyType>,
+ sec_trust_evaluate_with_error: Symbol<'a, SecTrustEvaluateWithErrorType>,
+ sec_string_constants: BTreeMap<SecStringConstant, String>,
+}
+
+rental! {
+ mod rent_libloading {
+ use super::*;
+
+ #[rental]
+ pub struct RentedSecurityFramework {
+ library: Box<Library>, // Library needs to be StableDeref, hence the Box
+ functions: SecurityFrameworkFunctions<'library>,
+ }
+ }
+}
+
+/// 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 {
+ rental: Option<rent_libloading::RentedSecurityFramework>,
+}
+
+impl SecurityFramework {
+ fn new() -> SecurityFramework {
+ let library = match Library::new("/System/Library/Frameworks/Security.framework/Security") {
+ Ok(library) => library,
+ Err(_) => return SecurityFramework { rental: None },
+ };
+ match rent_libloading::RentedSecurityFramework::try_new::<_, ()>(
+ Box::new(library),
+ |library| unsafe {
+ let sec_key_create_signature = library
+ .get::<SecKeyCreateSignatureType>(b"SecKeyCreateSignature\0")
+ .map_err(|_| ())?;
+ let sec_key_copy_attributes = library
+ .get::<SecKeyCopyAttributesType>(b"SecKeyCopyAttributes\0")
+ .map_err(|_| ())?;
+ let sec_key_copy_external_representation = library
+ .get::<SecKeyCopyExternalRepresentationType>(
+ b"SecKeyCopyExternalRepresentation\0",
+ )
+ .map_err(|_| ())?;
+ let sec_certificate_copy_normalized_issuer_sequence = library
+ .get::<SecCertificateCopyNormalizedIssuerSequenceType>(
+ b"SecCertificateCopyNormalizedIssuerSequence\0",
+ )
+ .map_err(|_| ())?;
+ let sec_certificate_copy_normalized_subject_sequence = library
+ .get::<SecCertificateCopyNormalizedSubjectSequenceType>(
+ b"SecCertificateCopyNormalizedSubjectSequence\0",
+ )
+ .map_err(|_| ())?;
+ let sec_certificate_copy_key = library
+ .get::<SecCertificateCopyKeyType>(b"SecCertificateCopyKey\0")
+ .map_err(|_| ())?;
+ let sec_trust_evaluate_with_error = library
+ .get::<SecTrustEvaluateWithErrorType>(b"SecTrustEvaluateWithError\0")
+ .map_err(|_| ())?;
+ let mut sec_string_constants = BTreeMap::new();
+ let strings_to_load = vec![
+ (
+ b"kSecKeyAlgorithmECDSASignatureDigestX962SHA1\0".as_ref(),
+ SecStringConstant::SecKeyAlgorithmECDSASignatureDigestX962SHA1,
+ ),
+ (
+ b"kSecKeyAlgorithmECDSASignatureDigestX962SHA256\0".as_ref(),
+ SecStringConstant::SecKeyAlgorithmECDSASignatureDigestX962SHA256,
+ ),
+ (
+ b"kSecKeyAlgorithmECDSASignatureDigestX962SHA384\0".as_ref(),
+ SecStringConstant::SecKeyAlgorithmECDSASignatureDigestX962SHA384,
+ ),
+ (
+ b"kSecKeyAlgorithmECDSASignatureDigestX962SHA512\0".as_ref(),
+ SecStringConstant::SecKeyAlgorithmECDSASignatureDigestX962SHA512,
+ ),
+ (
+ b"kSecKeyAlgorithmRSASignatureDigestPKCS1v15Raw\0".as_ref(),
+ SecStringConstant::SecKeyAlgorithmRSASignatureDigestPKCS1v15Raw,
+ ),
+ (
+ 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,
+ ),
+ (
+ b"kSecAttrKeyTypeECSECPrimeRandom\0".as_ref(),
+ SecStringConstant::SecAttrKeyTypeECSECPrimeRandom,
+ ),
+ ];
+ for (symbol_name, sec_string_constant) in strings_to_load {
+ let cfstring_symbol = library
+ .get::<*const CFStringRef>(symbol_name)
+ .map_err(|_| ())?;
+ let cfstring = CFString::wrap_under_create_rule(**cfstring_symbol);
+ sec_string_constants.insert(sec_string_constant, cfstring.to_string());
+ }
+ Ok(SecurityFrameworkFunctions {
+ sec_key_create_signature,
+ sec_key_copy_attributes,
+ sec_key_copy_external_representation,
+ sec_certificate_copy_normalized_issuer_sequence,
+ sec_certificate_copy_normalized_subject_sequence,
+ sec_certificate_copy_key,
+ sec_trust_evaluate_with_error,
+ sec_string_constants,
+ })
+ },
+ ) {
+ Ok(rental) => SecurityFramework {
+ rental: Some(rental),
+ },
+ Err(_) => SecurityFramework { rental: None },
+ }
+ }
+
+ /// SecKeyCreateSignature is available in macOS 10.12
+ fn sec_key_create_signature(
+ &self,
+ key: &SecKey,
+ algorithm: SecKeyAlgorithm,
+ data_to_sign: &CFData,
+ ) -> Result<CFData, ()> {
+ match &self.rental {
+ Some(rental) => rental.rent(|framework| unsafe {
+ let mut error = std::ptr::null_mut();
+ let result = (framework.sec_key_create_signature)(
+ key.as_concrete_TypeRef(),
+ algorithm,
+ data_to_sign.as_concrete_TypeRef(),
+ &mut error,
+ );
+ if result.is_null() {
+ let error = CFError::wrap_under_create_rule(error);
+ error!("SecKeyCreateSignature failed: {}", error);
+ return Err(());
+ }
+ Ok(CFData::wrap_under_create_rule(result))
+ }),
+ None => Err(()),
+ }
+ }
+
+ /// SecKeyCopyAttributes is available in macOS 10.12
+ fn sec_key_copy_attributes<T>(&self, key: &SecKey) -> Result<CFDictionary<CFString, T>, ()> {
+ match &self.rental {
+ Some(rental) => rental.rent(|framework| unsafe {
+ let result = (framework.sec_key_copy_attributes)(key.as_concrete_TypeRef());
+ if result.is_null() {
+ error!("SecKeyCopyAttributes failed");
+ return Err(());
+ }
+ Ok(CFDictionary::wrap_under_create_rule(result))
+ }),
+ None => Err(()),
+ }
+ }
+
+ /// SecKeyCopyExternalRepresentation is available in macOS 10.12
+ fn sec_key_copy_external_representation(&self, key: &SecKey) -> Result<CFData, ()> {
+ match &self.rental {
+ Some(rental) => rental.rent(|framework| unsafe {
+ let mut error = std::ptr::null_mut();
+ let result = (framework.sec_key_copy_external_representation)(
+ key.as_concrete_TypeRef(),
+ &mut error,
+ );
+ if result.is_null() {
+ let error = CFError::wrap_under_create_rule(error);
+ error!("SecKeyCopyExternalRepresentation failed: {}", error);
+ return Err(());
+ }
+ Ok(CFData::wrap_under_create_rule(result))
+ }),
+ None => Err(()),
+ }
+ }
+
+ /// SecCertificateCopyNormalizedIssuerSequence is available in macOS 10.12.4
+ fn sec_certificate_copy_normalized_issuer_sequence(
+ &self,
+ certificate: &SecCertificate,
+ ) -> Result<CFData, ()> {
+ match &self.rental {
+ Some(rental) => rental.rent(|framework| unsafe {
+ let result = (framework.sec_certificate_copy_normalized_issuer_sequence)(
+ certificate.as_concrete_TypeRef(),
+ );
+ if result.is_null() {
+ error!("SecCertificateCopyNormalizedIssuerSequence failed");
+ return Err(());
+ }
+ Ok(CFData::wrap_under_create_rule(result))
+ }),
+ None => Err(()),
+ }
+ }
+
+ /// SecCertificateCopyNormalizedSubjectSequence is available in macOS 10.12.4
+ fn sec_certificate_copy_normalized_subject_sequence(
+ &self,
+ certificate: &SecCertificate,
+ ) -> Result<CFData, ()> {
+ match &self.rental {
+ Some(rental) => rental.rent(|framework| unsafe {
+ let result = (framework.sec_certificate_copy_normalized_subject_sequence)(
+ certificate.as_concrete_TypeRef(),
+ );
+ if result.is_null() {
+ error!("SecCertificateCopyNormalizedSubjectSequence failed");
+ return Err(());
+ }
+ Ok(CFData::wrap_under_create_rule(result))
+ }),
+ None => Err(()),
+ }
+ }
+
+ /// SecCertificateCopyKey is available in macOS 10.14
+ fn sec_certificate_copy_key(&self, certificate: &SecCertificate) -> Result<SecKey, ()> {
+ match &self.rental {
+ Some(rental) => rental.rent(|framework| unsafe {
+ let result =
+ (framework.sec_certificate_copy_key)(certificate.as_concrete_TypeRef());
+ if result.is_null() {
+ error!("SecCertificateCopyKey failed");
+ return Err(());
+ }
+ Ok(SecKey::wrap_under_create_rule(result))
+ }),
+ None => Err(()),
+ }
+ }
+
+ /// SecTrustEvaluateWithError is available in macOS 10.14
+ fn sec_trust_evaluate_with_error(&self, trust: &SecTrust) -> Result<bool, ()> {
+ match &self.rental {
+ Some(rental) => rental.rent(|framework| unsafe {
+ Ok((framework.sec_trust_evaluate_with_error)(
+ trust.as_concrete_TypeRef(),
+ std::ptr::null_mut(),
+ ))
+ }),
+ None => Err(()),
+ }
+ }
+
+ fn get_sec_string_constant(
+ &self,
+ sec_string_constant: SecStringConstant,
+ ) -> Result<CFString, ()> {
+ match &self.rental {
+ Some(rental) => rental.rent(|framework| {
+ match framework.sec_string_constants.get(&sec_string_constant) {
+ Some(string) => Ok(CFString::new(string)),
+ None => Err(()),
+ }
+ }),
+ None => Err(()),
+ }
+ }
+}
+
+lazy_static! {
+ static ref SECURITY_FRAMEWORK: SecurityFramework = SecurityFramework::new();
+}
+
+fn sec_identity_copy_certificate(identity: &SecIdentity) -> Result<SecCertificate, ()> {
+ let mut certificate = std::ptr::null();
+ let status =
+ unsafe { SecIdentityCopyCertificate(identity.as_concrete_TypeRef(), &mut certificate) };
+ if status != errSecSuccess {
+ error!("SecIdentityCopyCertificate failed: {}", status);
+ return Err(());
+ }
+ if certificate.is_null() {
+ error!("couldn't get certificate from identity?");
+ return Err(());
+ }
+ Ok(unsafe { SecCertificate::wrap_under_create_rule(certificate) })
+}
+
+fn sec_certificate_copy_subject_summary(certificate: &SecCertificate) -> Result<CFString, ()> {
+ let result = unsafe { SecCertificateCopySubjectSummary(certificate.as_concrete_TypeRef()) };
+ if result.is_null() {
+ error!("SecCertificateCopySubjectSummary failed");
+ return Err(());
+ }
+ Ok(unsafe { CFString::wrap_under_create_rule(result) })
+}
+
+fn sec_certificate_copy_data(certificate: &SecCertificate) -> Result<CFData, ()> {
+ let result = unsafe { SecCertificateCopyData(certificate.as_concrete_TypeRef()) };
+ if result.is_null() {
+ error!("SecCertificateCopyData failed");
+ return Err(());
+ }
+ Ok(unsafe { CFData::wrap_under_create_rule(result) })
+}
+
+fn sec_identity_copy_private_key(identity: &SecIdentity) -> Result<SecKey, ()> {
+ let mut key = std::ptr::null();
+ let status = unsafe { SecIdentityCopyPrivateKey(identity.as_concrete_TypeRef(), &mut key) };
+ if status != errSecSuccess {
+ error!("SecIdentityCopyPrivateKey failed: {}", status);
+ return Err(());
+ }
+ if key.is_null() {
+ error!("SecIdentityCopyPrivateKey didn't set key?");
+ return Err(());
+ }
+ 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, ()> {
+ let certificate = sec_identity_copy_certificate(identity)?;
+ Cert::new_from_certificate(&certificate)
+ }
+
+ fn new_from_certificate(certificate: &SecCertificate) -> Result<Cert, ()> {
+ 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 issuer =
+ SECURITY_FRAMEWORK.sec_certificate_copy_normalized_issuer_sequence(certificate)?;
+ let serial_number = read_encoded_serial_number(&der)?;
+ let subject =
+ SECURITY_FRAMEWORK.sec_certificate_copy_normalized_subject_sequence(certificate)?;
+ Ok(Cert {
+ class: serialize_uint(CKO_CERTIFICATE)?,
+ token: serialize_uint(CK_TRUE)?,
+ id,
+ label: label.to_string().into_bytes(),
+ value: der,
+ issuer: issuer.bytes().to_vec(),
+ serial_number,
+ subject: subject.bytes().to_vec(),
+ })
+ }
+
+ fn class(&self) -> &[u8] {
+ &self.class
+ }
+
+ fn token(&self) -> &[u8] {
+ &self.token
+ }
+
+ pub 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
+ }
+
+ fn matches(&self, attrs: &[(CK_ATTRIBUTE_TYPE, Vec<u8>)]) -> bool {
+ 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)
+ }
+}
+
+#[derive(Clone, Copy, Debug)]
+pub enum KeyType {
+ EC(usize),
+ RSA,
+}
+
+enum SignParams {
+ EC(CFString),
+ RSA(CFString),
+}
+
+impl SignParams {
+ fn new(
+ key_type: KeyType,
+ data_len: usize,
+ params: &Option<CK_RSA_PKCS_PSS_PARAMS>,
+ ) -> Result<SignParams, ()> {
+ match key_type {
+ KeyType::EC(_) => SignParams::new_ec_params(data_len),
+ KeyType::RSA => SignParams::new_rsa_params(params),
+ }
+ }
+
+ fn new_ec_params(data_len: usize) -> Result<SignParams, ()> {
+ let algorithm_id = match data_len {
+ 20 => SecStringConstant::SecKeyAlgorithmECDSASignatureDigestX962SHA1,
+ 32 => SecStringConstant::SecKeyAlgorithmECDSASignatureDigestX962SHA256,
+ 48 => SecStringConstant::SecKeyAlgorithmECDSASignatureDigestX962SHA384,
+ 64 => SecStringConstant::SecKeyAlgorithmECDSASignatureDigestX962SHA512,
+ _ => {
+ error!(
+ "Unexpected digested signature input length for ECDSA: {}",
+ data_len
+ );
+ return Err(());
+ }
+ };
+ let algorithm = SECURITY_FRAMEWORK.get_sec_string_constant(algorithm_id)?;
+ Ok(SignParams::EC(algorithm))
+ }
+
+ fn new_rsa_params(params: &Option<CK_RSA_PKCS_PSS_PARAMS>) -> Result<SignParams, ()> {
+ let pss_params = match params {
+ Some(pss_params) => pss_params,
+ None => {
+ return Ok(SignParams::RSA(
+ SECURITY_FRAMEWORK.get_sec_string_constant(
+ SecStringConstant::SecKeyAlgorithmRSASignatureDigestPKCS1v15Raw,
+ )?,
+ ));
+ }
+ };
+ 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,
+ _ => {
+ error!(
+ "unsupported algorithm to use with RSA-PSS: {}",
+ unsafe_packed_field_access!(pss_params.hashAlg)
+ );
+ return Err(());
+ }
+ };
+ SECURITY_FRAMEWORK.get_sec_string_constant(algorithm_id)?
+ };
+ Ok(SignParams::RSA(algorithm))
+ }
+
+ fn get_algorithm(&self) -> SecKeyAlgorithm {
+ match self {
+ SignParams::EC(algorithm) => algorithm.as_concrete_TypeRef(),
+ SignParams::RSA(algorithm) => algorithm.as_concrete_TypeRef(),
+ }
+ }
+}
+
+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,
+}
+
+impl Key {
+ fn new(identity: &SecIdentity) -> Result<Key, ()> {
+ 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 = SECURITY_FRAMEWORK
+ .get_sec_string_constant(SecStringConstant::SecAttrKeyTypeECSECPrimeRandom)?;
+ let (key_type_enum, key_type_attribute) =
+ if key_type.as_concrete_TypeRef() == unsafe { kSecAttrKeyTypeRSA } {
+ let public_key = SECURITY_FRAMEWORK.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(()),
+ };
+ match key_size_in_bits {
+ 256 => ec_params = Some(OID_BYTES_SECP256R1.to_vec()),
+ 384 => ec_params = Some(OID_BYTES_SECP384R1.to_vec()),
+ 521 => ec_params = Some(OID_BYTES_SECP521R1.to_vec()),
+ _ => {
+ error!("unsupported EC key");
+ return Err(());
+ }
+ }
+ let coordinate_width = (key_size_in_bits as usize + 7) / 8;
+ (KeyType::EC(coordinate_width), CKK_EC)
+ } else {
+ error!("unsupported key type");
+ return Err(());
+ };
+
+ 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,
+ })
+ }
+
+ fn class(&self) -> &[u8] {
+ &self.class
+ }
+
+ fn token(&self) -> &[u8] {
+ &self.token
+ }
+
+ pub 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 matches(&self, attrs: &[(CK_ATTRIBUTE_TYPE, Vec<u8>)]) -> bool {
+ 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,
+ }
+ }
+
+ pub fn get_signature_length(
+ &self,
+ data: &[u8],
+ params: &Option<CK_RSA_PKCS_PSS_PARAMS>,
+ ) -> Result<usize, ()> {
+ // 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.
+ pub fn sign(
+ &self,
+ data: &[u8],
+ params: &Option<CK_RSA_PKCS_PSS_PARAMS>,
+ ) -> Result<Vec<u8>, ()> {
+ let key = sec_identity_copy_private_key(&self.identity)?;
+ let sign_params = SignParams::new(self.key_type_enum, data.len(), params)?;
+ let signing_algorithm = sign_params.get_algorithm();
+ let data = CFData::from_buffer(data);
+ let signature =
+ SECURITY_FRAMEWORK.sec_key_create_signature(&key, signing_algorithm, &data)?;
+ 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(());
+ }
+ 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)
+ }
+}
+
+pub enum Object {
+ Cert(Cert),
+ Key(Key),
+}
+
+impl Object {
+ pub 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;
+ }
+ match self {
+ Object::Cert(cert) => cert.matches(attrs),
+ Object::Key(key) => key.matches(attrs),
+ }
+ }
+
+ pub fn get_attribute(&self, attribute: CK_ATTRIBUTE_TYPE) -> Option<&[u8]> {
+ match self {
+ Object::Cert(cert) => cert.get_attribute(attribute),
+ Object::Key(key) => key.get_attribute(attribute),
+ }
+ }
+}
+
+pub const SUPPORTED_ATTRIBUTES: &[CK_ATTRIBUTE_TYPE] = &[
+ CKA_CLASS,
+ CKA_TOKEN,
+ CKA_LABEL,
+ CKA_ID,
+ CKA_VALUE,
+ CKA_ISSUER,
+ CKA_SERIAL_NUMBER,
+ CKA_SUBJECT,
+ CKA_PRIVATE,
+ CKA_KEY_TYPE,
+ CKA_MODULUS,
+ CKA_EC_PARAMS,
+];
+
+fn get_key_attribute<T: TCFType + Clone>(key: &SecKey, attr: CFStringRef) -> Result<T, ()> {
+ let attributes: CFDictionary<CFString, T> = SECURITY_FRAMEWORK.sec_key_copy_attributes(&key)?;
+ match attributes.find(attr as *const _) {
+ Some(value) => Ok((*value).clone()),
+ None => Err(()),
+ }
+}
+
+// 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>, ()> {
+ let certificate = sec_identity_copy_certificate(identity)?;
+ let policy = unsafe { SecPolicyCreateSSL(false, std::ptr::null()) };
+ if policy.is_null() {
+ error!("SecPolicyCreateSSL failed");
+ return Err(());
+ }
+ 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 {
+ error!("SecTrustCreateWithCertificates failed: {}", status);
+ return Err(());
+ }
+ if trust.is_null() {
+ error!("trust is null?");
+ return Err(());
+ }
+ 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 {
+ error!("SecTrustSetNetworkFetchAllowed failed: {}", status);
+ return Err(());
+ }
+ // 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(|_| ())?);
+ 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 fn list_objects() -> Vec<Object> {
+ let mut objects = 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 != errSecSuccess {
+ error!("SecItemCopyMatching failed: {}", status);
+ return objects;
+ }
+ if result.is_null() {
+ debug!("no client certs?");
+ return objects;
+ }
+ 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) {
+ objects.push(Object::Cert(cert));
+ objects.push(Object::Key(key));
+ } else {
+ continue;
+ }
+ if let Ok(issuers) = get_issuers(&identity) {
+ for issuer in issuers {
+ if let Ok(cert) = Cert::new_from_certificate(&issuer) {
+ objects.push(Object::Cert(cert));
+ }
+ }
+ }
+ }
+ objects
+}
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..a422d2e2bc
--- /dev/null
+++ b/security/manager/ssl/osclientcerts/src/backend_windows.rs
@@ -0,0 +1,933 @@
+/* -*- 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::types::*;
+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, *};
+
+use crate::manager::SlotType;
+use crate::util::*;
+
+// 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>, ()> {
+ 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(|_| ())?,
+ )
+ };
+ if subject_dn_len as usize != subject_dn_string_bytes.len() {
+ return Err(());
+ }
+ 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, ()> {
+ 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 issuer = unsafe {
+ slice::from_raw_parts(cert_info.Issuer.pbData, cert_info.Issuer.cbData as usize)
+ };
+ let issuer = issuer.to_vec();
+ let serial_number = read_encoded_serial_number(&value)?;
+ let subject = unsafe {
+ slice::from_raw_parts(cert_info.Subject.pbData, cert_info.Subject.cbData as usize)
+ };
+ let subject = subject.to_vec();
+ 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
+ }
+
+ pub 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
+ }
+
+ 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, ()> {
+ 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
+ {
+ error!(
+ "CryptAcquireCertificatePrivateKey failed: 0x{:x}",
+ GetLastError()
+ );
+ return Err(());
+ }
+ }
+ if must_free == 0 {
+ error!("CryptAcquireCertificatePrivateKey returned shared key handle");
+ return Err(());
+ }
+ 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>, ()> {
+ 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>, ()> {
+ 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(|_| ())?,
+ 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 {
+ error!(
+ "NCryptSignHash failed trying to get signature buffer length, {}",
+ status
+ );
+ return Err(());
+ }
+ 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(|_| ())?,
+ signature.as_mut_ptr(),
+ signature_len,
+ &mut final_signature_len,
+ flags,
+ )
+ };
+ if status != 0 {
+ error!("NCryptSignHash failed signing data {}", status);
+ return Err(());
+ }
+ if final_signature_len != signature_len {
+ error!(
+ "NCryptSignHash: inconsistent signature lengths? {} != {}",
+ final_signature_len, signature_len
+ );
+ return Err(());
+ }
+ 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>, ()> {
+ if params.is_some() {
+ error!("non-None signature params cannot be used with CryptoAPI");
+ return Err(());
+ }
+ // 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(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
+ {
+ error!(
+ "CryptSignHash failed trying to get signature buffer length: 0x{:x}",
+ unsafe { GetLastError() }
+ );
+ return Err(());
+ }
+ 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
+ {
+ error!("CryptSignHash failed signing data: 0x{:x}", unsafe {
+ GetLastError()
+ });
+ return Err(());
+ }
+ if final_signature_len != signature_len {
+ error!(
+ "CryptSignHash: inconsistent signature lengths? {} != {}",
+ final_signature_len, signature_len
+ );
+ return Err(());
+ }
+ // 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, ()> {
+ let alg = match hash_bytes.len() {
+ 20 => CALG_SHA1,
+ 32 => CALG_SHA_256,
+ 48 => CALG_SHA_384,
+ 64 => CALG_SHA_512,
+ _ => {
+ error!(
+ "HCryptHash::new: invalid hash of length {}",
+ hash_bytes.len()
+ );
+ return Err(());
+ }
+ };
+ let mut hash: HCRYPTHASH = 0;
+ if unsafe { CryptCreateHash(*hcryptprov, alg, 0, 0, &mut hash) } != 1 {
+ error!("CryptCreateHash failed: 0x{:x}", unsafe { GetLastError() });
+ return Err(());
+ }
+ if unsafe { CryptSetHashParam(hash, HP_HASHVAL, hash_bytes.as_ptr(), 0) } != 1 {
+ error!("CryptSetHashParam failed: 0x{:x}", unsafe {
+ GetLastError()
+ });
+ return Err(());
+ }
+ 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, ()> {
+ // 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,
+ _ => {
+ error!(
+ "unsupported algorithm to use with RSA-PSS: {}",
+ unsafe_packed_field_access!(pss_params.hashAlg)
+ );
+ return Err(());
+ }
+ };
+ 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.
+#[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,
+}
+
+impl Key {
+ fn new(cert_context: PCCERT_CONTEXT) -> Result<Key, ()> {
+ 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(|_| ())?;
+ let (key_type_enum, key_type_attribute) = if algorithm_oid == szOID_RSA_RSA {
+ if spki.PublicKey.cUnusedBits != 0 {
+ return Err(());
+ }
+ 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(());
+ };
+ 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,
+ })
+ }
+
+ fn class(&self) -> &[u8] {
+ &self.class
+ }
+
+ fn token(&self) -> &[u8] {
+ &self.token
+ }
+
+ pub 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 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,
+ }
+ }
+
+ pub fn get_signature_length(
+ &self,
+ data: &[u8],
+ params: &Option<CK_RSA_PKCS_PSS_PARAMS>,
+ ) -> Result<usize, ()> {
+ match self.sign_internal(data, params, false) {
+ Ok(dummy_signature_bytes) => Ok(dummy_signature_bytes.len()),
+ Err(()) => Err(()),
+ }
+ }
+
+ pub fn sign(
+ &self,
+ data: &[u8],
+ params: &Option<CK_RSA_PKCS_PSS_PARAMS>,
+ ) -> Result<Vec<u8>, ()> {
+ self.sign_internal(data, params, true)
+ }
+
+ /// 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(
+ &self,
+ data: &[u8],
+ params: &Option<CK_RSA_PKCS_PSS_PARAMS>,
+ do_signature: bool,
+ ) -> Result<Vec<u8>, ()> {
+ // Acquiring a handle on the key can cause the OS to show some UI to the user, so we do this
+ // as late as possible (i.e. here).
+ let key = KeyHandle::from_cert(&self.cert)?;
+ key.sign(data, params, do_signature, self.key_type_enum)
+ }
+}
+
+/// A helper enum that represents the two types of PKCS #11 objects we support: certificates and
+/// keys.
+pub enum Object {
+ Cert(Cert),
+ Key(Key),
+}
+
+impl Object {
+ pub fn matches(&self, slot_type: SlotType, attrs: &[(CK_ATTRIBUTE_TYPE, Vec<u8>)]) -> bool {
+ match self {
+ Object::Cert(cert) => cert.matches(slot_type, attrs),
+ Object::Key(key) => key.matches(slot_type, attrs),
+ }
+ }
+
+ pub fn get_attribute(&self, attribute: CK_ATTRIBUTE_TYPE) -> Option<&[u8]> {
+ match self {
+ Object::Cert(cert) => cert.get_attribute(attribute),
+ Object::Key(key) => key.get_attribute(attribute),
+ }
+ }
+}
+
+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 }
+ }
+}
+
+pub const SUPPORTED_ATTRIBUTES: &[CK_ATTRIBUTE_TYPE] = &[
+ CKA_CLASS,
+ CKA_TOKEN,
+ CKA_LABEL,
+ CKA_ID,
+ CKA_VALUE,
+ CKA_ISSUER,
+ CKA_SERIAL_NUMBER,
+ CKA_SUBJECT,
+ CKA_PRIVATE,
+ CKA_KEY_TYPE,
+ CKA_MODULUS,
+ CKA_EC_PARAMS,
+];
+
+// 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
+}
+
+/// 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.
+pub fn list_objects() -> Vec<Object> {
+ let mut objects = Vec::new();
+ let location_flags = CERT_SYSTEM_STORE_CURRENT_USER // TODO: loop over multiple locations
+ | CERT_STORE_OPEN_EXISTING_FLAG
+ | CERT_STORE_READONLY_FLAG;
+ let store_name = match CString::new("My") {
+ Ok(store_name) => store_name,
+ Err(null_error) => {
+ error!("CString::new given input with a null byte: {}", null_error);
+ return objects;
+ }
+ };
+ 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() {
+ error!("CertOpenStore failed");
+ return objects;
+ }
+ 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,
+ };
+ objects.push(Object::Cert(cert));
+ objects.push(Object::Key(key));
+ }
+ None => {}
+ };
+ for cert_context in cert_contexts.iter().skip(1) {
+ if let Ok(cert) = Cert::new(*cert_context) {
+ objects.push(Object::Cert(cert));
+ }
+ }
+ }
+ objects
+}
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..14aff193b3
--- /dev/null
+++ b/security/manager/ssl/osclientcerts/src/bindings_macos.rs
@@ -0,0 +1,58 @@
+/* -*- 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 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;
+}
diff --git a/security/manager/ssl/osclientcerts/src/lib.rs b/security/manager/ssl/osclientcerts/src/lib.rs
new file mode 100644
index 0000000000..f53f45f142
--- /dev/null
+++ b/security/manager/ssl/osclientcerts/src/lib.rs
@@ -0,0 +1,1208 @@
+/* -*- 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;
+#[cfg(target_os = "macos")]
+#[macro_use]
+extern crate rental;
+extern crate sha2;
+#[cfg(target_os = "windows")]
+extern crate winapi;
+
+use pkcs11::types::*;
+use std::sync::Mutex;
+use std::thread;
+
+mod manager;
+#[macro_use]
+mod util;
+#[cfg(target_os = "macos")]
+mod backend_macos;
+#[cfg(target_os = "windows")]
+mod backend_windows;
+
+use manager::{ManagerProxy, SlotType};
+
+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),*) => {
+ let message = format!($($message),*);
+ $log_level!("{:?} {}", thread::current().id(), message);
+ };
+}
+
+/// This gets called to initialize the module. For this implementation, this consists of
+/// instantiating the `ManagerProxy`.
+extern "C" fn C_Initialize(_pInitArgs: CK_C_INITIALIZE_ARGS_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!();
+ match manager_guard.replace(ManagerProxy::new()) {
+ 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(()) => {
+ log_with_thread_id!(error, "C_Finalize: CKR_DEVICE_ERROR");
+ 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 in 0..mechanisms.len() {
+ unsafe {
+ *pMechanismList.offset(i as isize) = mechanisms[i];
+ }
+ }
+ }
+ 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(()) => {
+ log_with_thread_id!(error, "C_OpenSession: open_session failed");
+ 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(()) => {
+ log_with_thread_id!(error, "C_CloseAllSessions: close_all_sessions failed");
+ 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 {
+ let attr = unsafe { &*pTemplate.offset(i as isize) };
+ attr_types.push(attr.attrType);
+ }
+ 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(()) => {
+ log_with_thread_id!(error, "C_GetAttributeValue: CKR_ARGUMENTS_BAD");
+ 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 in 0..ulCount as usize {
+ let mut attr = unsafe { &mut *pTemplate.offset(i as isize) };
+ // NB: the safety of this array access depends on the length check above
+ if let Some(attr_value) = &values[i] {
+ 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.attrType) {
+ 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.attrType)),
+ };
+ let value =
+ unsafe { std::slice::from_raw_parts(attr.pValue as *const u8, attr.ulValueLen as usize) };
+ log_with_thread_id!(
+ trace,
+ "{}CK_ATTRIBUTE {{ attrType: {}, 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 {
+ let attr = unsafe { &*pTemplate.offset(i as isize) };
+ trace_attr(" ", &attr);
+ let slice = unsafe {
+ std::slice::from_raw_parts(attr.pValue as *const u8, attr.ulValueLen as usize)
+ };
+ attrs.push((attr.attrType, 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(()) => {
+ log_with_thread_id!(error, "C_FindObjectsInit: CKR_ARGUMENTS_BAD");
+ 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(()) => {
+ log_with_thread_id!(error, "C_FindObjects: CKR_ARGUMENTS_BAD");
+ 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(()) => {
+ log_with_thread_id!(error, "C_FindObjectsFinal: clear_search failed");
+ 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(()) => {
+ log_with_thread_id!(error, "C_SignInit: CKR_GENERAL_ERROR");
+ 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(()) => {
+ log_with_thread_id!(error, "C_Sign: get_signature_length failed");
+ 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(()) => {
+ log_with_thread_id!(error, "C_Sign: sign failed");
+ 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),
+};
+
+/// This is the only function this module exposes. NSS calls it to obtain the list of functions
+/// comprising this module.
+#[no_mangle]
+pub extern "C" fn C_GetFunctionList(ppFunctionList: CK_FUNCTION_LIST_PTR_PTR) -> CK_RV {
+ if ppFunctionList.is_null() {
+ return CKR_ARGUMENTS_BAD;
+ }
+ unsafe {
+ *ppFunctionList = &mut FUNCTION_LIST;
+ }
+ CKR_OK
+}
+
+#[cfg_attr(target_os = "macos", link(name = "Security", kind = "framework"))]
+extern "C" {}
diff --git a/security/manager/ssl/osclientcerts/src/manager.rs b/security/manager/ssl/osclientcerts/src/manager.rs
new file mode 100644
index 0000000000..de4be1272a
--- /dev/null
+++ b/security/manager/ssl/osclientcerts/src/manager.rs
@@ -0,0 +1,614 @@
+/* -*- 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/. */
+
+use pkcs11::types::*;
+use std::collections::{BTreeMap, BTreeSet};
+
+#[cfg(target_os = "macos")]
+use crate::backend_macos as backend;
+#[cfg(target_os = "windows")]
+use crate::backend_windows as backend;
+use crate::util::*;
+use backend::*;
+
+use std::sync::mpsc::{channel, Receiver, Sender};
+use std::thread;
+use std::thread::JoinHandle;
+use std::time::{Duration, Instant};
+
+/// Helper enum to differentiate between sessions on the modern slot and sessions on the legacy
+/// slot. The former is for EC keys and RSA keys that can be used with RSA-PSS whereas the latter is
+/// for RSA keys that cannot be used with RSA-PSS.
+#[derive(Clone, Copy, PartialEq)]
+pub enum SlotType {
+ Modern,
+ Legacy,
+}
+
+/// Helper type for sending `ManagerArguments` to the real `Manager`.
+type ManagerArgumentsSender = Sender<ManagerArguments>;
+/// Helper type for receiving `ManagerReturnValue`s from the real `Manager`.
+type ManagerReturnValueReceiver = Receiver<ManagerReturnValue>;
+
+/// Helper enum that encapsulates arguments to send from the `ManagerProxy` to the real `Manager`.
+/// `ManagerArguments::Stop` is a special variant that stops the background thread and drops the
+/// `Manager`.
+enum ManagerArguments {
+ OpenSession(SlotType),
+ CloseSession(CK_SESSION_HANDLE),
+ CloseAllSessions(SlotType),
+ StartSearch(CK_SESSION_HANDLE, Vec<(CK_ATTRIBUTE_TYPE, Vec<u8>)>),
+ Search(CK_SESSION_HANDLE, usize),
+ ClearSearch(CK_SESSION_HANDLE),
+ GetAttributes(CK_OBJECT_HANDLE, Vec<CK_ATTRIBUTE_TYPE>),
+ StartSign(
+ CK_SESSION_HANDLE,
+ CK_OBJECT_HANDLE,
+ Option<CK_RSA_PKCS_PSS_PARAMS>,
+ ),
+ GetSignatureLength(CK_SESSION_HANDLE, Vec<u8>),
+ Sign(CK_SESSION_HANDLE, Vec<u8>),
+ Stop,
+}
+
+/// Helper enum that encapsulates return values from the real `Manager` that are sent back to the
+/// `ManagerProxy`. `ManagerReturnValue::Stop` is a special variant that indicates that the
+/// `Manager` will stop.
+enum ManagerReturnValue {
+ OpenSession(Result<CK_SESSION_HANDLE, ()>),
+ CloseSession(Result<(), ()>),
+ CloseAllSessions(Result<(), ()>),
+ StartSearch(Result<(), ()>),
+ Search(Result<Vec<CK_OBJECT_HANDLE>, ()>),
+ ClearSearch(Result<(), ()>),
+ GetAttributes(Result<Vec<Option<Vec<u8>>>, ()>),
+ StartSign(Result<(), ()>),
+ GetSignatureLength(Result<usize, ()>),
+ Sign(Result<Vec<u8>, ()>),
+ Stop(Result<(), ()>),
+}
+
+/// Helper macro to implement the body of each public `ManagerProxy` function. Takes a
+/// `ManagerProxy` instance (should always be `self`), a `ManagerArguments` representing the
+/// `Manager` function to call and the arguments to use, and the qualified type of the expected
+/// `ManagerReturnValue` that will be received from the `Manager` when it is done.
+macro_rules! manager_proxy_fn_impl {
+ ($manager:ident, $argument_enum:expr, $return_type:path) => {
+ match $manager.proxy_call($argument_enum) {
+ Ok($return_type(result)) => result,
+ Ok(_) => {
+ error!("unexpected return value from manager");
+ Err(())
+ }
+ Err(()) => Err(()),
+ }
+ };
+}
+
+/// `ManagerProxy` synchronously proxies calls from any thread to the `Manager` that runs on a
+/// single thread. This is necessary because the underlying OS APIs in use are not guaranteed to be
+/// thread-safe (e.g. they may use thread-local storage). Using it should be identical to using the
+/// real `Manager`.
+pub struct ManagerProxy {
+ sender: ManagerArgumentsSender,
+ receiver: ManagerReturnValueReceiver,
+ thread_handle: Option<JoinHandle<()>>,
+}
+
+impl ManagerProxy {
+ pub fn new() -> ManagerProxy {
+ let (proxy_sender, manager_receiver) = channel();
+ let (manager_sender, proxy_receiver) = channel();
+ let thread_handle = thread::spawn(move || {
+ let mut real_manager = Manager::new();
+ loop {
+ let arguments = match manager_receiver.recv() {
+ Ok(arguments) => arguments,
+ Err(e) => {
+ error!("error recv()ing arguments from ManagerProxy: {}", e);
+ break;
+ }
+ };
+ let results = match arguments {
+ ManagerArguments::OpenSession(slot_type) => {
+ ManagerReturnValue::OpenSession(real_manager.open_session(slot_type))
+ }
+ ManagerArguments::CloseSession(session_handle) => {
+ ManagerReturnValue::CloseSession(real_manager.close_session(session_handle))
+ }
+ ManagerArguments::CloseAllSessions(slot_type) => {
+ ManagerReturnValue::CloseAllSessions(
+ real_manager.close_all_sessions(slot_type),
+ )
+ }
+ ManagerArguments::StartSearch(session, attrs) => {
+ ManagerReturnValue::StartSearch(real_manager.start_search(session, &attrs))
+ }
+ ManagerArguments::Search(session, max_objects) => {
+ ManagerReturnValue::Search(real_manager.search(session, max_objects))
+ }
+ ManagerArguments::ClearSearch(session) => {
+ ManagerReturnValue::ClearSearch(real_manager.clear_search(session))
+ }
+ ManagerArguments::GetAttributes(object_handle, attr_types) => {
+ ManagerReturnValue::GetAttributes(
+ real_manager.get_attributes(object_handle, attr_types),
+ )
+ }
+ ManagerArguments::StartSign(session, key_handle, params) => {
+ ManagerReturnValue::StartSign(
+ real_manager.start_sign(session, key_handle, params),
+ )
+ }
+ ManagerArguments::GetSignatureLength(session, data) => {
+ ManagerReturnValue::GetSignatureLength(
+ real_manager.get_signature_length(session, &data),
+ )
+ }
+ ManagerArguments::Sign(session, data) => {
+ ManagerReturnValue::Sign(real_manager.sign(session, &data))
+ }
+ ManagerArguments::Stop => {
+ debug!("ManagerArguments::Stop received - stopping Manager thread.");
+ ManagerReturnValue::Stop(Ok(()))
+ }
+ };
+ let stop_after_send = match &results {
+ &ManagerReturnValue::Stop(_) => true,
+ _ => false,
+ };
+ match manager_sender.send(results) {
+ Ok(()) => {}
+ Err(e) => {
+ error!("error send()ing results from Manager: {}", e);
+ break;
+ }
+ }
+ if stop_after_send {
+ break;
+ }
+ }
+ });
+ ManagerProxy {
+ sender: proxy_sender,
+ receiver: proxy_receiver,
+ thread_handle: Some(thread_handle),
+ }
+ }
+
+ fn proxy_call(&self, args: ManagerArguments) -> Result<ManagerReturnValue, ()> {
+ match self.sender.send(args) {
+ Ok(()) => {}
+ Err(e) => {
+ error!("error send()ing arguments to Manager: {}", e);
+ return Err(());
+ }
+ };
+ let result = match self.receiver.recv() {
+ Ok(result) => result,
+ Err(e) => {
+ error!("error recv()ing result from Manager: {}", e);
+ return Err(());
+ }
+ };
+ Ok(result)
+ }
+
+ pub fn open_session(&mut self, slot_type: SlotType) -> Result<CK_SESSION_HANDLE, ()> {
+ manager_proxy_fn_impl!(
+ self,
+ ManagerArguments::OpenSession(slot_type),
+ ManagerReturnValue::OpenSession
+ )
+ }
+
+ pub fn close_session(&mut self, session: CK_SESSION_HANDLE) -> Result<(), ()> {
+ manager_proxy_fn_impl!(
+ self,
+ ManagerArguments::CloseSession(session),
+ ManagerReturnValue::CloseSession
+ )
+ }
+
+ pub fn close_all_sessions(&mut self, slot_type: SlotType) -> Result<(), ()> {
+ manager_proxy_fn_impl!(
+ self,
+ ManagerArguments::CloseAllSessions(slot_type),
+ ManagerReturnValue::CloseAllSessions
+ )
+ }
+
+ pub fn start_search(
+ &mut self,
+ session: CK_SESSION_HANDLE,
+ attrs: Vec<(CK_ATTRIBUTE_TYPE, Vec<u8>)>,
+ ) -> Result<(), ()> {
+ manager_proxy_fn_impl!(
+ self,
+ ManagerArguments::StartSearch(session, attrs),
+ ManagerReturnValue::StartSearch
+ )
+ }
+
+ pub fn search(
+ &mut self,
+ session: CK_SESSION_HANDLE,
+ max_objects: usize,
+ ) -> Result<Vec<CK_OBJECT_HANDLE>, ()> {
+ manager_proxy_fn_impl!(
+ self,
+ ManagerArguments::Search(session, max_objects),
+ ManagerReturnValue::Search
+ )
+ }
+
+ pub fn clear_search(&mut self, session: CK_SESSION_HANDLE) -> Result<(), ()> {
+ manager_proxy_fn_impl!(
+ self,
+ ManagerArguments::ClearSearch(session),
+ ManagerReturnValue::ClearSearch
+ )
+ }
+
+ pub fn get_attributes(
+ &self,
+ object_handle: CK_OBJECT_HANDLE,
+ attr_types: Vec<CK_ATTRIBUTE_TYPE>,
+ ) -> Result<Vec<Option<Vec<u8>>>, ()> {
+ manager_proxy_fn_impl!(
+ self,
+ ManagerArguments::GetAttributes(object_handle, attr_types,),
+ ManagerReturnValue::GetAttributes
+ )
+ }
+
+ pub fn start_sign(
+ &mut self,
+ session: CK_SESSION_HANDLE,
+ key_handle: CK_OBJECT_HANDLE,
+ params: Option<CK_RSA_PKCS_PSS_PARAMS>,
+ ) -> Result<(), ()> {
+ manager_proxy_fn_impl!(
+ self,
+ ManagerArguments::StartSign(session, key_handle, params),
+ ManagerReturnValue::StartSign
+ )
+ }
+
+ pub fn get_signature_length(
+ &self,
+ session: CK_SESSION_HANDLE,
+ data: Vec<u8>,
+ ) -> Result<usize, ()> {
+ manager_proxy_fn_impl!(
+ self,
+ ManagerArguments::GetSignatureLength(session, data),
+ ManagerReturnValue::GetSignatureLength
+ )
+ }
+
+ pub fn sign(&mut self, session: CK_SESSION_HANDLE, data: Vec<u8>) -> Result<Vec<u8>, ()> {
+ manager_proxy_fn_impl!(
+ self,
+ ManagerArguments::Sign(session, data),
+ ManagerReturnValue::Sign
+ )
+ }
+
+ pub fn stop(&mut self) -> Result<(), ()> {
+ manager_proxy_fn_impl!(self, ManagerArguments::Stop, ManagerReturnValue::Stop)?;
+ let thread_handle = match self.thread_handle.take() {
+ Some(thread_handle) => thread_handle,
+ None => {
+ error!("stop should only be called once");
+ return Err(());
+ }
+ };
+ match thread_handle.join() {
+ Ok(()) => {}
+ Err(e) => {
+ error!("manager thread panicked: {:?}", e);
+ return Err(());
+ }
+ };
+ Ok(())
+ }
+}
+
+// Determines if the attributes of a given search correspond to NSS looking for all certificates or
+// private keys. Returns true if so, and false otherwise.
+// These searches are of the form:
+// { { type: CKA_TOKEN, value: [1] },
+// { type: CKA_CLASS, value: [CKO_CERTIFICATE or CKO_PRIVATE_KEY, as serialized bytes] } }
+// (although not necessarily in that order - see nssToken_TraverseCertificates and
+// nssToken_FindPrivateKeys)
+fn search_is_for_all_certificates_or_keys(
+ attrs: &[(CK_ATTRIBUTE_TYPE, Vec<u8>)],
+) -> Result<bool, ()> {
+ if attrs.len() != 2 {
+ return Ok(false);
+ }
+ let token_bytes = vec![1 as u8];
+ let mut found_token = false;
+ let cko_certificate_bytes = serialize_uint(CKO_CERTIFICATE)?;
+ let cko_private_key_bytes = serialize_uint(CKO_PRIVATE_KEY)?;
+ let mut found_certificate_or_private_key = false;
+ for (attr_type, attr_value) in attrs.iter() {
+ if attr_type == &CKA_TOKEN && attr_value == &token_bytes {
+ found_token = true;
+ }
+ if attr_type == &CKA_CLASS
+ && (attr_value == &cko_certificate_bytes || attr_value == &cko_private_key_bytes)
+ {
+ found_certificate_or_private_key = true;
+ }
+ }
+ Ok(found_token && found_certificate_or_private_key)
+}
+
+/// The `Manager` keeps track of the state of this module with respect to the PKCS #11
+/// specification. This includes what sessions are open, which search and sign operations are
+/// ongoing, and what objects are known and by what handle.
+struct Manager {
+ /// A map of session to session type (modern or legacy). Sessions can be created (opened) and
+ /// later closed.
+ sessions: BTreeMap<CK_SESSION_HANDLE, SlotType>,
+ /// A map of searches to PKCS #11 object handles that match those searches.
+ searches: BTreeMap<CK_SESSION_HANDLE, Vec<CK_OBJECT_HANDLE>>,
+ /// A map of sign operations to a pair of the object handle and optionally some params being
+ /// used by each one.
+ signs: BTreeMap<CK_SESSION_HANDLE, (CK_OBJECT_HANDLE, Option<CK_RSA_PKCS_PSS_PARAMS>)>,
+ /// A map of object handles to the underlying objects.
+ objects: BTreeMap<CK_OBJECT_HANDLE, Object>,
+ /// A set of certificate identifiers (not the same as handles).
+ cert_ids: BTreeSet<Vec<u8>>,
+ /// A set of key identifiers (not the same as handles). For each id in this set, there should be
+ /// a corresponding identical id in the `cert_ids` set, and vice-versa.
+ key_ids: BTreeSet<Vec<u8>>,
+ /// The next session handle to hand out.
+ next_session: CK_SESSION_HANDLE,
+ /// The next object handle to hand out.
+ next_handle: CK_OBJECT_HANDLE,
+ /// The last time the implementation looked for new objects in the backend.
+ /// The implementation does this search no more than once every 3 seconds.
+ last_scan_time: Option<Instant>,
+}
+
+impl Manager {
+ pub fn new() -> Manager {
+ let mut manager = Manager {
+ sessions: BTreeMap::new(),
+ searches: BTreeMap::new(),
+ signs: BTreeMap::new(),
+ objects: BTreeMap::new(),
+ cert_ids: BTreeSet::new(),
+ key_ids: BTreeSet::new(),
+ next_session: 1,
+ next_handle: 1,
+ last_scan_time: None,
+ };
+ manager.maybe_find_new_objects();
+ manager
+ }
+
+ /// When a new `Manager` is created and when a new session is opened (provided at least 3
+ /// seconds have elapsed since the last session was opened), this searches for certificates and
+ /// keys to expose. We de-duplicate previously-found certificates and keys by / keeping track of
+ /// their IDs.
+ fn maybe_find_new_objects(&mut self) {
+ let now = Instant::now();
+ match self.last_scan_time {
+ Some(last_scan_time) => {
+ if now.duration_since(last_scan_time) < Duration::new(3, 0) {
+ return;
+ }
+ }
+ None => {}
+ }
+ self.last_scan_time = Some(now);
+ let objects = list_objects();
+ debug!("found {} objects", objects.len());
+ for object in objects {
+ match &object {
+ Object::Cert(cert) => {
+ if self.cert_ids.contains(cert.id()) {
+ continue;
+ }
+ self.cert_ids.insert(cert.id().to_vec());
+ let handle = self.get_next_handle();
+ self.objects.insert(handle, object);
+ }
+ Object::Key(key) => {
+ if self.key_ids.contains(key.id()) {
+ continue;
+ }
+ self.key_ids.insert(key.id().to_vec());
+ let handle = self.get_next_handle();
+ self.objects.insert(handle, object);
+ }
+ }
+ }
+ }
+
+ pub fn open_session(&mut self, slot_type: SlotType) -> Result<CK_SESSION_HANDLE, ()> {
+ let next_session = self.next_session;
+ self.next_session += 1;
+ self.sessions.insert(next_session, slot_type);
+ Ok(next_session)
+ }
+
+ pub fn close_session(&mut self, session: CK_SESSION_HANDLE) -> Result<(), ()> {
+ self.sessions.remove(&session).ok_or(()).map(|_| ())
+ }
+
+ pub fn close_all_sessions(&mut self, slot_type: SlotType) -> Result<(), ()> {
+ let mut to_remove = Vec::new();
+ for (session, open_slot_type) in self.sessions.iter() {
+ if slot_type == *open_slot_type {
+ to_remove.push(*session);
+ }
+ }
+ for session in to_remove {
+ if self.sessions.remove(&session).is_none() {
+ return Err(());
+ }
+ }
+ Ok(())
+ }
+
+ fn get_next_handle(&mut self) -> CK_OBJECT_HANDLE {
+ let next_handle = self.next_handle;
+ self.next_handle += 1;
+ next_handle
+ }
+
+ /// PKCS #11 specifies that search operations happen in three phases: setup, get any matches
+ /// (this part may be repeated if the caller uses a small buffer), and end. This implementation
+ /// does all of the work up front and gathers all matching objects during setup and retains them
+ /// until they are retrieved and consumed via `search`.
+ pub fn start_search(
+ &mut self,
+ session: CK_SESSION_HANDLE,
+ attrs: &[(CK_ATTRIBUTE_TYPE, Vec<u8>)],
+ ) -> Result<(), ()> {
+ let slot_type = match self.sessions.get(&session) {
+ Some(slot_type) => *slot_type,
+ None => return Err(()),
+ };
+ // If the search is for an attribute we don't support, no objects will match. This check
+ // saves us having to look through all of our objects.
+ for (attr, _) in attrs {
+ if !SUPPORTED_ATTRIBUTES.contains(attr) {
+ self.searches.insert(session, Vec::new());
+ return Ok(());
+ }
+ }
+ // When NSS wants to find all certificates or all private keys, it will perform a search
+ // with a particular set of attributes. This implementation uses these searches as an
+ // indication for the backend to re-scan for new objects from tokens that may have been
+ // inserted or certificates that may have been imported into the OS. Since these searches
+ // are relatively rare, this minimizes the impact of doing these re-scans.
+ if search_is_for_all_certificates_or_keys(attrs)? {
+ self.maybe_find_new_objects();
+ }
+ let mut handles = Vec::new();
+ for (handle, object) in &self.objects {
+ if object.matches(slot_type, attrs) {
+ handles.push(*handle);
+ }
+ }
+ self.searches.insert(session, handles);
+ Ok(())
+ }
+
+ /// Given a session and a maximum number of object handles to return, attempts to retrieve up to
+ /// that many objects from the corresponding search. Updates the search so those objects are not
+ /// returned repeatedly. `max_objects` must be non-zero.
+ pub fn search(
+ &mut self,
+ session: CK_SESSION_HANDLE,
+ max_objects: usize,
+ ) -> Result<Vec<CK_OBJECT_HANDLE>, ()> {
+ if max_objects == 0 {
+ return Err(());
+ }
+ match self.searches.get_mut(&session) {
+ Some(search) => {
+ let split_at = if max_objects >= search.len() {
+ 0
+ } else {
+ search.len() - max_objects
+ };
+ let to_return = search.split_off(split_at);
+ if to_return.len() > max_objects {
+ error!(
+ "search trying to return too many handles: {} > {}",
+ to_return.len(),
+ max_objects
+ );
+ return Err(());
+ }
+ Ok(to_return)
+ }
+ None => Err(()),
+ }
+ }
+
+ pub fn clear_search(&mut self, session: CK_SESSION_HANDLE) -> Result<(), ()> {
+ self.searches.remove(&session);
+ Ok(())
+ }
+
+ pub fn get_attributes(
+ &self,
+ object_handle: CK_OBJECT_HANDLE,
+ attr_types: Vec<CK_ATTRIBUTE_TYPE>,
+ ) -> Result<Vec<Option<Vec<u8>>>, ()> {
+ let object = match self.objects.get(&object_handle) {
+ Some(object) => object,
+ None => return Err(()),
+ };
+ let mut results = Vec::with_capacity(attr_types.len());
+ for attr_type in attr_types {
+ let result = match object.get_attribute(attr_type) {
+ Some(value) => Some(value.to_owned()),
+ None => None,
+ };
+ results.push(result);
+ }
+ Ok(results)
+ }
+
+ /// The way NSS uses PKCS #11 to sign data happens in two phases: setup and sign. This
+ /// implementation makes a note of which key is to be used (if it exists) during setup. When the
+ /// caller finishes with the sign operation, this implementation retrieves the key handle and
+ /// performs the signature.
+ pub fn start_sign(
+ &mut self,
+ session: CK_SESSION_HANDLE,
+ key_handle: CK_OBJECT_HANDLE,
+ params: Option<CK_RSA_PKCS_PSS_PARAMS>,
+ ) -> Result<(), ()> {
+ if self.signs.contains_key(&session) {
+ return Err(());
+ }
+ match self.objects.get(&key_handle) {
+ Some(Object::Key(_)) => {}
+ _ => return Err(()),
+ };
+ self.signs.insert(session, (key_handle, params));
+ Ok(())
+ }
+
+ pub fn get_signature_length(
+ &self,
+ session: CK_SESSION_HANDLE,
+ data: &[u8],
+ ) -> Result<usize, ()> {
+ let (key_handle, params) = match self.signs.get(&session) {
+ Some((key_handle, params)) => (key_handle, params),
+ None => return Err(()),
+ };
+ let key = match self.objects.get(&key_handle) {
+ Some(Object::Key(key)) => key,
+ _ => return Err(()),
+ };
+ key.get_signature_length(data, params)
+ }
+
+ pub fn sign(&mut self, session: CK_SESSION_HANDLE, data: &[u8]) -> Result<Vec<u8>, ()> {
+ // Performing the signature (via C_Sign, which is the only way we support) finishes the sign
+ // operation, so it needs to be removed here.
+ let (key_handle, params) = match self.signs.remove(&session) {
+ Some((key_handle, params)) => (key_handle, params),
+ None => return Err(()),
+ };
+ let key = match self.objects.get(&key_handle) {
+ Some(Object::Key(key)) => key,
+ _ => return Err(()),
+ };
+ key.sign(data, &params)
+ }
+}
diff --git a/security/manager/ssl/osclientcerts/src/util.rs b/security/manager/ssl/osclientcerts/src/util.rs
new file mode 100644
index 0000000000..fc176ee751
--- /dev/null
+++ b/security/manager/ssl/osclientcerts/src/util.rs
@@ -0,0 +1,463 @@
+/* -*- 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/. */
+
+use byteorder::{BigEndian, NativeEndian, ReadBytesExt, WriteBytesExt};
+use std::convert::TryInto;
+
+/// Accessing fields of packed structs is unsafe (it may be undefined behavior if the field isn't
+/// aligned). Since we're implementing a PKCS#11 module, we already have to trust the caller not to
+/// give us bad data, so normally we would deal with this by adding an unsafe block. If we do that,
+/// though, the compiler complains that the unsafe block is unnecessary. Thus, we use this macro to
+/// annotate the unsafe block to silence the compiler.
+macro_rules! unsafe_packed_field_access {
+ ($e:expr) => {{
+ #[allow(unused_unsafe)]
+ let tmp = unsafe { $e };
+ tmp
+ }};
+}
+
+#[cfg(target_os = "macos")]
+pub const OID_BYTES_SECP256R1: &[u8] =
+ &[0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07];
+#[cfg(target_os = "macos")]
+pub const OID_BYTES_SECP384R1: &[u8] = &[0x06, 0x05, 0x2b, 0x81, 0x04, 0x00, 0x22];
+#[cfg(target_os = "macos")]
+pub const OID_BYTES_SECP521R1: &[u8] = &[0x06, 0x05, 0x2b, 0x81, 0x04, 0x00, 0x23];
+
+// This is a helper function to take a value and lay it out in memory how
+// PKCS#11 is expecting it.
+pub fn serialize_uint<T: TryInto<u64>>(value: T) -> Result<Vec<u8>, ()> {
+ let value_size = std::mem::size_of::<T>();
+ let mut value_buf = Vec::with_capacity(value_size);
+ let value_as_u64 = value.try_into().map_err(|_| ())?;
+ value_buf
+ .write_uint::<NativeEndian>(value_as_u64, value_size)
+ .map_err(|_| ())?;
+ Ok(value_buf)
+}
+
+/// Given a slice of DER bytes representing an RSA public key, extracts the bytes of the modulus
+/// as an unsigned integer. Also verifies that the public exponent is present (again as an
+/// unsigned integer). Finally verifies that reading these values consumes the entirety of the
+/// slice.
+/// RSAPublicKey ::= SEQUENCE {
+/// modulus INTEGER, -- n
+/// publicExponent INTEGER -- e
+/// }
+pub fn read_rsa_modulus(public_key: &[u8]) -> Result<Vec<u8>, ()> {
+ let mut sequence = Sequence::new(public_key)?;
+ let modulus_value = sequence.read_unsigned_integer()?;
+ let _exponent = sequence.read_unsigned_integer()?;
+ if !sequence.at_end() {
+ return Err(());
+ }
+ Ok(modulus_value.to_vec())
+}
+
+/// Given a slice of DER bytes representing a DigestInfo, extracts the bytes of the digest.
+///
+/// DigestInfo ::= SEQUENCE {
+/// digestAlgorithm DigestAlgorithmIdentifier,
+/// digest Digest }
+///
+/// DigestAlgorithmIdentifier ::= AlgorithmIdentifier
+///
+/// AlgorithmIdentifier ::= SEQUENCE {
+/// algorithm OBJECT IDENTIFIER,
+/// parameters ANY DEFINED BY algorithm OPTIONAL }
+///
+/// Digest ::= OCTET STRING
+#[cfg(target_os = "windows")]
+pub fn read_digest<'a>(digest_info: &'a [u8]) -> Result<&'a [u8], ()> {
+ let mut sequence = Sequence::new(digest_info)?;
+ let _ = sequence.read_sequence()?;
+ let digest = sequence.read_octet_string()?;
+ if !sequence.at_end() {
+ error!("read_digest: extra input");
+ return Err(());
+ }
+ Ok(digest)
+}
+
+/// Given a slice of DER bytes representing an ECDSA signature, extracts the bytes of `r` and `s`
+/// as unsigned integers. Also verifies that this consumes the entirety of the slice.
+/// Ecdsa-Sig-Value ::= SEQUENCE {
+/// r INTEGER,
+/// s INTEGER }
+#[cfg(target_os = "macos")]
+pub fn read_ec_sig_point<'a>(signature: &'a [u8]) -> Result<(&'a [u8], &'a [u8]), ()> {
+ let mut sequence = Sequence::new(signature)?;
+ let r = sequence.read_unsigned_integer()?;
+ let s = sequence.read_unsigned_integer()?;
+ if !sequence.at_end() {
+ return Err(());
+ }
+ Ok((r, s))
+}
+
+/// Given a slice of DER bytes representing an X.509 certificate, extracts the encoded serial
+/// number. Does not verify that the remainder of the certificate is in any way well-formed.
+/// Certificate ::= SEQUENCE {
+/// tbsCertificate TBSCertificate,
+/// signatureAlgorithm AlgorithmIdentifier,
+/// signatureValue BIT STRING }
+///
+/// TBSCertificate ::= SEQUENCE {
+/// version [0] EXPLICIT Version DEFAULT v1,
+/// serialNumber CertificateSerialNumber,
+/// ...
+///
+/// CertificateSerialNumber ::= INTEGER
+pub fn read_encoded_serial_number(certificate: &[u8]) -> Result<Vec<u8>, ()> {
+ let mut certificate_sequence = Sequence::new(certificate)?;
+ let mut tbs_certificate_sequence = certificate_sequence.read_sequence()?;
+ let _version = tbs_certificate_sequence.read_tagged_value(0)?;
+ let serial_number = tbs_certificate_sequence.read_encoded_sequence_component(INTEGER)?;
+ Ok(serial_number)
+}
+
+/// Helper macro for reading some bytes from a slice while checking the slice is long enough.
+/// Returns a pair consisting of a slice of the bytes read and a slice of the rest of the bytes
+/// from the original slice.
+macro_rules! try_read_bytes {
+ ($data:ident, $len:expr) => {{
+ if $data.len() < $len {
+ return Err(());
+ }
+ $data.split_at($len)
+ }};
+}
+
+/// ASN.1 tag identifying an integer.
+const INTEGER: u8 = 0x02;
+/// ASN.1 tag identifying an octet string.
+#[cfg(target_os = "windows")]
+const OCTET_STRING: u8 = 0x04;
+/// ASN.1 tag identifying a sequence.
+const SEQUENCE: u8 = 0x10;
+/// ASN.1 tag modifier identifying an item as constructed.
+const CONSTRUCTED: u8 = 0x20;
+/// ASN.1 tag modifier identifying an item as context-specific.
+const CONTEXT_SPECIFIC: u8 = 0x80;
+
+/// A helper struct for reading items from a DER SEQUENCE (in this case, all sequences are
+/// assumed to be CONSTRUCTED).
+struct Sequence<'a> {
+ /// The contents of the SEQUENCE.
+ contents: Der<'a>,
+}
+
+impl<'a> Sequence<'a> {
+ fn new(input: &'a [u8]) -> Result<Sequence<'a>, ()> {
+ let mut der = Der::new(input);
+ let (_, _, sequence_bytes) = der.read_tlv(SEQUENCE | CONSTRUCTED)?;
+ // We're assuming we want to consume the entire input for now.
+ if !der.at_end() {
+ return Err(());
+ }
+ Ok(Sequence {
+ contents: Der::new(sequence_bytes),
+ })
+ }
+
+ // TODO: we're not exhaustively validating this integer
+ fn read_unsigned_integer(&mut self) -> Result<&'a [u8], ()> {
+ let (_, _, bytes) = self.contents.read_tlv(INTEGER)?;
+ if bytes.is_empty() {
+ return Err(());
+ }
+ // There may be a leading zero (we should also check that the first bit
+ // of the rest of the integer is set).
+ if bytes[0] == 0 && bytes.len() > 1 {
+ let (_, integer) = bytes.split_at(1);
+ Ok(integer)
+ } else {
+ Ok(bytes)
+ }
+ }
+
+ #[cfg(target_os = "windows")]
+ fn read_octet_string(&mut self) -> Result<&'a [u8], ()> {
+ let (_, _, bytes) = self.contents.read_tlv(OCTET_STRING)?;
+ Ok(bytes)
+ }
+
+ fn read_sequence(&mut self) -> Result<Sequence<'a>, ()> {
+ let (_, _, sequence_bytes) = self.contents.read_tlv(SEQUENCE | CONSTRUCTED)?;
+ Ok(Sequence {
+ contents: Der::new(sequence_bytes),
+ })
+ }
+
+ fn read_tagged_value(&mut self, tag: u8) -> Result<&'a [u8], ()> {
+ let (_, _, tagged_value_bytes) = self
+ .contents
+ .read_tlv(CONTEXT_SPECIFIC | CONSTRUCTED | tag)?;
+ Ok(tagged_value_bytes)
+ }
+
+ fn read_encoded_sequence_component(&mut self, tag: u8) -> Result<Vec<u8>, ()> {
+ let (tag, length, value) = self.contents.read_tlv(tag)?;
+ let mut encoded_component_bytes = length;
+ encoded_component_bytes.insert(0, tag);
+ encoded_component_bytes.extend_from_slice(value);
+ Ok(encoded_component_bytes)
+ }
+
+ fn at_end(&self) -> bool {
+ self.contents.at_end()
+ }
+}
+
+/// A helper struct for reading DER data. The contents are treated like a cursor, so its position
+/// is updated as data is read.
+struct Der<'a> {
+ contents: &'a [u8],
+}
+
+impl<'a> Der<'a> {
+ fn new(contents: &'a [u8]) -> Der<'a> {
+ Der { contents }
+ }
+
+ // In theory, a caller could encounter an error and try another operation, in which case we may
+ // be in an inconsistent state. As long as this implementation isn't exposed to code that would
+ // use it incorrectly (i.e. it stays in this module and we only expose a stateless API), it
+ // should be safe.
+ /// Given an expected tag, reads the next (tag, lengh, value) from the contents. Most
+ /// consumers will only be interested in the value, but some may want the entire encoded
+ /// contents, in which case the returned tuple can be concatenated.
+ fn read_tlv(&mut self, tag: u8) -> Result<(u8, Vec<u8>, &'a [u8]), ()> {
+ let contents = self.contents;
+ let (tag_read, rest) = try_read_bytes!(contents, 1);
+ if tag_read[0] != tag {
+ return Err(());
+ }
+ let mut accumulated_length_bytes = Vec::with_capacity(4);
+ let (length1, rest) = try_read_bytes!(rest, 1);
+ accumulated_length_bytes.extend_from_slice(length1);
+ let (length, to_read_from) = if length1[0] < 0x80 {
+ (length1[0] as usize, rest)
+ } else if length1[0] == 0x81 {
+ let (length, rest) = try_read_bytes!(rest, 1);
+ accumulated_length_bytes.extend_from_slice(length);
+ if length[0] < 0x80 {
+ return Err(());
+ }
+ (length[0] as usize, rest)
+ } else if length1[0] == 0x82 {
+ let (lengths, rest) = try_read_bytes!(rest, 2);
+ accumulated_length_bytes.extend_from_slice(lengths);
+ let length = (&mut &lengths[..])
+ .read_u16::<BigEndian>()
+ .map_err(|_| ())?;
+ if length < 256 {
+ return Err(());
+ }
+ (length as usize, rest)
+ } else {
+ return Err(());
+ };
+ let (contents, rest) = try_read_bytes!(to_read_from, length);
+ self.contents = rest;
+ Ok((tag, accumulated_length_bytes, contents))
+ }
+
+ fn at_end(&self) -> bool {
+ self.contents.is_empty()
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn der_test_empty_input() {
+ let input = Vec::new();
+ let mut der = Der::new(&input);
+ assert!(der.read_tlv(INTEGER).is_err());
+ }
+
+ #[test]
+ fn der_test_no_length() {
+ let input = vec![INTEGER];
+ let mut der = Der::new(&input);
+ assert!(der.read_tlv(INTEGER).is_err());
+ }
+
+ #[test]
+ fn der_test_empty_sequence() {
+ let input = vec![SEQUENCE, 0];
+ let mut der = Der::new(&input);
+ let read_result = der.read_tlv(SEQUENCE);
+ assert!(read_result.is_ok());
+ let (tag, length, sequence_bytes) = read_result.unwrap();
+ assert_eq!(tag, SEQUENCE);
+ assert_eq!(length, vec![0]);
+ assert_eq!(sequence_bytes.len(), 0);
+ assert!(der.at_end());
+ }
+
+ #[test]
+ fn der_test_not_at_end() {
+ let input = vec![SEQUENCE, 0, 1];
+ let mut der = Der::new(&input);
+ let read_result = der.read_tlv(SEQUENCE);
+ assert!(read_result.is_ok());
+ let (tag, length, sequence_bytes) = read_result.unwrap();
+ assert_eq!(tag, SEQUENCE);
+ assert_eq!(length, vec![0]);
+ assert_eq!(sequence_bytes.len(), 0);
+ assert!(!der.at_end());
+ }
+
+ #[test]
+ fn der_test_wrong_tag() {
+ let input = vec![SEQUENCE, 0];
+ let mut der = Der::new(&input);
+ assert!(der.read_tlv(INTEGER).is_err());
+ }
+
+ #[test]
+ fn der_test_truncated_two_byte_length() {
+ let input = vec![SEQUENCE, 0x81];
+ let mut der = Der::new(&input);
+ assert!(der.read_tlv(SEQUENCE).is_err());
+ }
+
+ #[test]
+ fn der_test_truncated_three_byte_length() {
+ let input = vec![SEQUENCE, 0x82, 1];
+ let mut der = Der::new(&input);
+ assert!(der.read_tlv(SEQUENCE).is_err());
+ }
+
+ #[test]
+ fn der_test_truncated_data() {
+ let input = vec![SEQUENCE, 20, 1];
+ let mut der = Der::new(&input);
+ assert!(der.read_tlv(SEQUENCE).is_err());
+ }
+
+ #[test]
+ fn der_test_sequence() {
+ let input = vec![
+ SEQUENCE, 20, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 0, 0,
+ ];
+ let mut der = Der::new(&input);
+ let result = der.read_tlv(SEQUENCE);
+ assert!(result.is_ok());
+ let (tag, length, value) = result.unwrap();
+ assert_eq!(tag, SEQUENCE);
+ assert_eq!(length, vec![20]);
+ assert_eq!(
+ value,
+ [1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 0, 0]
+ );
+ assert!(der.at_end());
+ }
+
+ #[test]
+ fn der_test_not_shortest_two_byte_length_encoding() {
+ let input = vec![SEQUENCE, 0x81, 1, 1];
+ let mut der = Der::new(&input);
+ assert!(der.read_tlv(SEQUENCE).is_err());
+ }
+
+ #[test]
+ fn der_test_not_shortest_three_byte_length_encoding() {
+ let input = vec![SEQUENCE, 0x82, 0, 1, 1];
+ let mut der = Der::new(&input);
+ assert!(der.read_tlv(SEQUENCE).is_err());
+ }
+
+ #[test]
+ fn der_test_indefinite_length_unsupported() {
+ let input = vec![SEQUENCE, 0x80, 1, 2, 3, 0x00, 0x00];
+ let mut der = Der::new(&input);
+ assert!(der.read_tlv(SEQUENCE).is_err());
+ }
+
+ #[test]
+ fn der_test_input_too_long() {
+ // This isn't valid DER (the contents of the SEQUENCE are truncated), but it demonstrates
+ // that we don't try to read too much if we're given a long length (and also that we don't
+ // support lengths 2^16 and up).
+ let input = vec![SEQUENCE, 0x83, 0x01, 0x00, 0x01, 1, 1, 1, 1];
+ let mut der = Der::new(&input);
+ assert!(der.read_tlv(SEQUENCE).is_err());
+ }
+
+ #[test]
+ fn empty_input_fails() {
+ let empty = Vec::new();
+ assert!(read_rsa_modulus(&empty).is_err());
+ #[cfg(target_os = "macos")]
+ assert!(read_ec_sig_point(&empty).is_err());
+ assert!(read_encoded_serial_number(&empty).is_err());
+ }
+
+ #[test]
+ fn empty_sequence_fails() {
+ let empty = vec![SEQUENCE | CONSTRUCTED];
+ assert!(read_rsa_modulus(&empty).is_err());
+ #[cfg(target_os = "macos")]
+ assert!(read_ec_sig_point(&empty).is_err());
+ assert!(read_encoded_serial_number(&empty).is_err());
+ }
+
+ #[test]
+ fn test_read_rsa_modulus() {
+ let rsa_key = include_bytes!("../test/rsa.bin");
+ let result = read_rsa_modulus(rsa_key);
+ assert!(result.is_ok());
+ let modulus = result.unwrap();
+ assert_eq!(modulus, include_bytes!("../test/modulus.bin").to_vec());
+ }
+
+ #[test]
+ fn test_read_serial_number() {
+ let certificate = include_bytes!("../test/certificate.bin");
+ let result = read_encoded_serial_number(certificate);
+ assert!(result.is_ok());
+ let serial_number = result.unwrap();
+ assert_eq!(
+ serial_number,
+ &[
+ 0x02, 0x14, 0x3f, 0xed, 0x7b, 0x43, 0x47, 0x8a, 0x53, 0x42, 0x5b, 0x0d, 0x50, 0xe1,
+ 0x37, 0x88, 0x2a, 0x20, 0x3f, 0x31, 0x17, 0x20
+ ]
+ );
+ }
+
+ #[test]
+ #[cfg(target_os = "windows")]
+ fn test_read_digest() {
+ // SEQUENCE
+ // SEQUENCE
+ // OBJECT IDENTIFIER 2.16.840.1.101.3.4.2.1 sha-256
+ // NULL
+ // OCTET STRING 1A7FCDB9A5F649F954885CFE145F3E93F0D1FA72BE980CC6EC82C70E1407C7D2
+ let digest_info = [
+ 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x1, 0x65, 0x03, 0x04, 0x02,
+ 0x01, 0x05, 0x00, 0x04, 0x20, 0x1a, 0x7f, 0xcd, 0xb9, 0xa5, 0xf6, 0x49, 0xf9, 0x54,
+ 0x88, 0x5c, 0xfe, 0x14, 0x5f, 0x3e, 0x93, 0xf0, 0xd1, 0xfa, 0x72, 0xbe, 0x98, 0x0c,
+ 0xc6, 0xec, 0x82, 0xc7, 0x0e, 0x14, 0x07, 0xc7, 0xd2,
+ ];
+ let result = read_digest(&digest_info);
+ assert!(result.is_ok());
+ let digest = result.unwrap();
+ assert_eq!(
+ digest,
+ &[
+ 0x1a, 0x7f, 0xcd, 0xb9, 0xa5, 0xf6, 0x49, 0xf9, 0x54, 0x88, 0x5c, 0xfe, 0x14, 0x5f,
+ 0x3e, 0x93, 0xf0, 0xd1, 0xfa, 0x72, 0xbe, 0x98, 0x0c, 0xc6, 0xec, 0x82, 0xc7, 0x0e,
+ 0x14, 0x07, 0xc7, 0xd2
+ ]
+ );
+ }
+}