summaryrefslogtreecommitdiffstats
path: root/security/manager/ssl/osclientcerts
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--security/manager/ssl/osclientcerts/Cargo.toml31
-rw-r--r--security/manager/ssl/osclientcerts/dynamic-library/moz.build33
-rw-r--r--security/manager/ssl/osclientcerts/dynamic-library/osclientcerts.symbols1
-rw-r--r--security/manager/ssl/osclientcerts/dynamic-library/stub.cpp8
-rw-r--r--security/manager/ssl/osclientcerts/moz.build9
-rw-r--r--security/manager/ssl/osclientcerts/src/backend_macos.rs849
-rw-r--r--security/manager/ssl/osclientcerts/src/backend_windows.rs914
-rw-r--r--security/manager/ssl/osclientcerts/src/bindings_macos.rs79
-rw-r--r--security/manager/ssl/osclientcerts/src/lib.rs1235
-rw-r--r--security/manager/ssl/osclientcerts/test/certificate.binbin0 -> 909 bytes
-rw-r--r--security/manager/ssl/osclientcerts/test/modulus.bin2
-rw-r--r--security/manager/ssl/osclientcerts/test/rsa.binbin0 -> 270 bytes
12 files changed, 3161 insertions, 0 deletions
diff --git a/security/manager/ssl/osclientcerts/Cargo.toml b/security/manager/ssl/osclientcerts/Cargo.toml
new file mode 100644
index 0000000000..2e364e3218
--- /dev/null
+++ b/security/manager/ssl/osclientcerts/Cargo.toml
@@ -0,0 +1,31 @@
+[package]
+name = "osclientcerts-static"
+version = "0.1.4"
+authors = ["Dana Keeler <dkeeler@mozilla.com>"]
+edition = "2018"
+description = "Platform-specific support for client authentication certificates in Firefox"
+repository = "https://github.com/mozkeeler/osclientcerts"
+license = "MPL-2.0"
+
+[dependencies]
+byteorder = "1.3"
+env_logger = {version = "0.10", default-features = false } # disable `regex` to reduce code size
+lazy_static = "1"
+log = "0.4"
+mozilla-central-workspace-hack = { path = "../../../../build/workspace-hack" }
+pkcs11-bindings = "0.1"
+rsclientcerts = { path = "../rsclientcerts" }
+sha2 = "0.10.2"
+
+[target."cfg(target_os = \"macos\")".dependencies.core-foundation]
+version = "0.9"
+
+[target."cfg(target_os = \"macos\")".dependencies.libloading]
+version = "0.7"
+
+[target."cfg(target_os = \"windows\")".dependencies.winapi]
+version = "0.3"
+features = ["wincrypt"]
+
+[lib]
+crate-type = ["staticlib"]
diff --git a/security/manager/ssl/osclientcerts/dynamic-library/moz.build b/security/manager/ssl/osclientcerts/dynamic-library/moz.build
new file mode 100644
index 0000000000..36c0341ca7
--- /dev/null
+++ b/security/manager/ssl/osclientcerts/dynamic-library/moz.build
@@ -0,0 +1,33 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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_LIBS += ["osclientcerts-static"]
+
+UNIFIED_SOURCES += [
+ "stub.cpp",
+]
+
+if CONFIG["OS_ARCH"] == "WINNT":
+ OS_LIBS += [
+ "advapi32",
+ "crypt32",
+ "kernel32",
+ "ncrypt",
+ "userenv",
+ "ws2_32",
+ "ntdll",
+ ]
+
+if CONFIG["OS_ARCH"] == "Darwin":
+ OS_LIBS += [
+ "-framework CoreFoundation",
+ "-framework Security",
+ ]
+
+SharedLibrary("osclientcerts")
+
+NoVisibilityFlags()
+SYMBOLS_FILE = "osclientcerts.symbols"
diff --git a/security/manager/ssl/osclientcerts/dynamic-library/osclientcerts.symbols b/security/manager/ssl/osclientcerts/dynamic-library/osclientcerts.symbols
new file mode 100644
index 0000000000..562ecea21d
--- /dev/null
+++ b/security/manager/ssl/osclientcerts/dynamic-library/osclientcerts.symbols
@@ -0,0 +1 @@
+C_GetFunctionList
diff --git a/security/manager/ssl/osclientcerts/dynamic-library/stub.cpp b/security/manager/ssl/osclientcerts/dynamic-library/stub.cpp
new file mode 100644
index 0000000000..bd20c15c4f
--- /dev/null
+++ b/security/manager/ssl/osclientcerts/dynamic-library/stub.cpp
@@ -0,0 +1,8 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 is an intentionally empty file. It is necessary for the build system to
+// successfully convert a static rust library into a dynamic library on
+// Windows.
diff --git a/security/manager/ssl/osclientcerts/moz.build b/security/manager/ssl/osclientcerts/moz.build
new file mode 100644
index 0000000000..fa484af6a4
--- /dev/null
+++ b/security/manager/ssl/osclientcerts/moz.build
@@ -0,0 +1,9 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+DIRS += ["dynamic-library"]
+
+RustLibrary("osclientcerts-static")
diff --git a/security/manager/ssl/osclientcerts/src/backend_macos.rs b/security/manager/ssl/osclientcerts/src/backend_macos.rs
new file mode 100644
index 0000000000..34386371aa
--- /dev/null
+++ b/security/manager/ssl/osclientcerts/src/backend_macos.rs
@@ -0,0 +1,849 @@
+/* -*- Mode: rust; rust-indent-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#![allow(non_upper_case_globals)]
+
+use core_foundation::array::*;
+use core_foundation::base::*;
+use core_foundation::boolean::*;
+use core_foundation::data::*;
+use core_foundation::dictionary::*;
+use core_foundation::error::*;
+use core_foundation::number::*;
+use core_foundation::string::*;
+use libloading::{Library, Symbol};
+use pkcs11_bindings::*;
+use rsclientcerts::error::{Error, ErrorType};
+use rsclientcerts::manager::{ClientCertsBackend, CryptokiObject, Sign, SlotType};
+use rsclientcerts::util::*;
+use sha2::{Digest, Sha256};
+use std::collections::BTreeMap;
+use std::convert::TryInto;
+use std::os::raw::c_void;
+
+// Normally we would generate this with a build script, but macos is
+// cross-compiled on linux, and we'd have to figure out e.g. include paths,
+// etc.. This is easier.
+include!("bindings_macos.rs");
+
+#[repr(C)]
+pub struct __SecIdentity(c_void);
+pub type SecIdentityRef = *const __SecIdentity;
+declare_TCFType!(SecIdentity, SecIdentityRef);
+impl_TCFType!(SecIdentity, SecIdentityRef, SecIdentityGetTypeID);
+
+#[repr(C)]
+pub struct __SecCertificate(c_void);
+pub type SecCertificateRef = *const __SecCertificate;
+declare_TCFType!(SecCertificate, SecCertificateRef);
+impl_TCFType!(SecCertificate, SecCertificateRef, SecCertificateGetTypeID);
+
+#[repr(C)]
+pub struct __SecKey(c_void);
+pub type SecKeyRef = *const __SecKey;
+declare_TCFType!(SecKey, SecKeyRef);
+impl_TCFType!(SecKey, SecKeyRef, SecKeyGetTypeID);
+
+#[repr(C)]
+pub struct __SecPolicy(c_void);
+pub type SecPolicyRef = *const __SecPolicy;
+declare_TCFType!(SecPolicy, SecPolicyRef);
+impl_TCFType!(SecPolicy, SecPolicyRef, SecPolicyGetTypeID);
+
+#[repr(C)]
+pub struct __SecTrust(c_void);
+pub type SecTrustRef = *const __SecTrust;
+declare_TCFType!(SecTrust, SecTrustRef);
+impl_TCFType!(SecTrust, SecTrustRef, SecTrustGetTypeID);
+
+type SecCertificateCopyKeyType = unsafe extern "C" fn(SecCertificateRef) -> SecKeyRef;
+type SecTrustEvaluateWithErrorType =
+ unsafe extern "C" fn(trust: SecTrustRef, error: *mut CFErrorRef) -> bool;
+
+#[derive(Ord, Eq, PartialOrd, PartialEq)]
+enum SecStringConstant {
+ // These are available in macOS 10.13
+ SecKeyAlgorithmRSASignatureDigestPSSSHA1,
+ SecKeyAlgorithmRSASignatureDigestPSSSHA256,
+ SecKeyAlgorithmRSASignatureDigestPSSSHA384,
+ SecKeyAlgorithmRSASignatureDigestPSSSHA512,
+}
+
+/// This implementation uses security framework functions and constants that
+/// are not provided by the version of the SDK we build with. To work around
+/// this, we attempt to open and dynamically load these functions and symbols
+/// at runtime. Unfortunately this does mean that if a user is not on a new
+/// enough version of macOS, they will not be able to use client certificates
+/// from their keychain in Firefox until they upgrade.
+struct SecurityFramework<'a> {
+ sec_certificate_copy_key: Symbol<'a, SecCertificateCopyKeyType>,
+ sec_trust_evaluate_with_error: Symbol<'a, SecTrustEvaluateWithErrorType>,
+ sec_string_constants: BTreeMap<SecStringConstant, String>,
+}
+
+lazy_static! {
+ static ref SECURITY_LIBRARY: Result<Library, String> = unsafe {
+ Library::new("/System/Library/Frameworks/Security.framework/Security")
+ .map_err(|e| e.to_string())
+ };
+}
+
+impl<'a> SecurityFramework<'a> {
+ fn new() -> Result<SecurityFramework<'a>, Error> {
+ let library = match &*SECURITY_LIBRARY {
+ Ok(library) => library,
+ Err(e) => return Err(error_here!(ErrorType::ExternalError, e.clone())),
+ };
+ let sec_certificate_copy_key = unsafe {
+ library
+ .get::<SecCertificateCopyKeyType>(b"SecCertificateCopyKey\0")
+ .map_err(|e| error_here!(ErrorType::ExternalError, e.to_string()))?
+ };
+ let sec_trust_evaluate_with_error = unsafe {
+ library
+ .get::<SecTrustEvaluateWithErrorType>(b"SecTrustEvaluateWithError\0")
+ .map_err(|e| error_here!(ErrorType::ExternalError, e.to_string()))?
+ };
+ let mut sec_string_constants = BTreeMap::new();
+ let strings_to_load = vec![
+ (
+ b"kSecKeyAlgorithmRSASignatureDigestPSSSHA1\0".as_ref(),
+ SecStringConstant::SecKeyAlgorithmRSASignatureDigestPSSSHA1,
+ ),
+ (
+ b"kSecKeyAlgorithmRSASignatureDigestPSSSHA256\0".as_ref(),
+ SecStringConstant::SecKeyAlgorithmRSASignatureDigestPSSSHA256,
+ ),
+ (
+ b"kSecKeyAlgorithmRSASignatureDigestPSSSHA384\0".as_ref(),
+ SecStringConstant::SecKeyAlgorithmRSASignatureDigestPSSSHA384,
+ ),
+ (
+ b"kSecKeyAlgorithmRSASignatureDigestPSSSHA512\0".as_ref(),
+ SecStringConstant::SecKeyAlgorithmRSASignatureDigestPSSSHA512,
+ ),
+ ];
+ for (symbol_name, sec_string_constant) in strings_to_load {
+ let cfstring_symbol = unsafe {
+ library
+ .get::<*const CFStringRef>(symbol_name)
+ .map_err(|e| error_here!(ErrorType::ExternalError, e.to_string()))?
+ };
+ let cfstring = unsafe { CFString::wrap_under_create_rule(**cfstring_symbol) };
+ sec_string_constants.insert(sec_string_constant, cfstring.to_string());
+ }
+ Ok(SecurityFramework {
+ sec_certificate_copy_key,
+ sec_trust_evaluate_with_error,
+ sec_string_constants,
+ })
+ }
+}
+
+struct SecurityFrameworkHolder<'a> {
+ framework: Result<SecurityFramework<'a>, Error>,
+}
+
+impl<'a> SecurityFrameworkHolder<'a> {
+ fn new() -> SecurityFrameworkHolder<'a> {
+ SecurityFrameworkHolder {
+ framework: SecurityFramework::new(),
+ }
+ }
+
+ /// SecCertificateCopyKey is available in macOS 10.14
+ fn sec_certificate_copy_key(&self, certificate: &SecCertificate) -> Result<SecKey, Error> {
+ match &self.framework {
+ Ok(framework) => unsafe {
+ let result =
+ (framework.sec_certificate_copy_key)(certificate.as_concrete_TypeRef());
+ if result.is_null() {
+ return Err(error_here!(ErrorType::ExternalError));
+ }
+ Ok(SecKey::wrap_under_create_rule(result))
+ },
+ Err(e) => Err(e.clone()),
+ }
+ }
+
+ /// SecTrustEvaluateWithError is available in macOS 10.14
+ fn sec_trust_evaluate_with_error(&self, trust: &SecTrust) -> Result<bool, Error> {
+ match &self.framework {
+ Ok(framework) => unsafe {
+ Ok((framework.sec_trust_evaluate_with_error)(
+ trust.as_concrete_TypeRef(),
+ std::ptr::null_mut(),
+ ))
+ },
+ Err(e) => Err(e.clone()),
+ }
+ }
+
+ fn get_sec_string_constant(
+ &self,
+ sec_string_constant: SecStringConstant,
+ ) -> Result<CFString, Error> {
+ match &self.framework {
+ Ok(framework) => match framework.sec_string_constants.get(&sec_string_constant) {
+ Some(string) => Ok(CFString::new(string)),
+ None => Err(error_here!(ErrorType::ExternalError)),
+ },
+ Err(e) => Err(e.clone()),
+ }
+ }
+}
+
+lazy_static! {
+ static ref SECURITY_FRAMEWORK: SecurityFrameworkHolder<'static> =
+ SecurityFrameworkHolder::new();
+}
+
+fn sec_key_create_signature(
+ key: &SecKey,
+ algorithm: SecKeyAlgorithm,
+ data: &CFData,
+) -> Result<CFData, Error> {
+ let mut error = std::ptr::null_mut();
+ let signature = unsafe {
+ SecKeyCreateSignature(
+ key.as_concrete_TypeRef(),
+ algorithm,
+ data.as_concrete_TypeRef(),
+ &mut error,
+ )
+ };
+ if signature.is_null() {
+ let error = unsafe { CFError::wrap_under_create_rule(error) };
+ return Err(error_here!(
+ ErrorType::ExternalError,
+ error.description().to_string()
+ ));
+ }
+ Ok(unsafe { CFData::wrap_under_create_rule(signature) })
+}
+
+fn sec_key_copy_attributes<T: TCFType>(key: &SecKey) -> CFDictionary<CFString, T> {
+ unsafe { CFDictionary::wrap_under_create_rule(SecKeyCopyAttributes(key.as_concrete_TypeRef())) }
+}
+
+fn sec_key_copy_external_representation(key: &SecKey) -> Result<CFData, Error> {
+ let mut error = std::ptr::null_mut();
+ let representation =
+ unsafe { SecKeyCopyExternalRepresentation(key.as_concrete_TypeRef(), &mut error) };
+ if representation.is_null() {
+ let error = unsafe { CFError::wrap_under_create_rule(error) };
+ return Err(error_here!(
+ ErrorType::ExternalError,
+ error.description().to_string()
+ ));
+ }
+ Ok(unsafe { CFData::wrap_under_create_rule(representation) })
+}
+
+fn sec_identity_copy_certificate(identity: &SecIdentity) -> Result<SecCertificate, Error> {
+ let mut certificate = std::ptr::null();
+ let status =
+ unsafe { SecIdentityCopyCertificate(identity.as_concrete_TypeRef(), &mut certificate) };
+ if status != errSecSuccess {
+ return Err(error_here!(ErrorType::ExternalError, status.to_string()));
+ }
+ if certificate.is_null() {
+ return Err(error_here!(ErrorType::ExternalError));
+ }
+ Ok(unsafe { SecCertificate::wrap_under_create_rule(certificate) })
+}
+
+fn sec_certificate_copy_subject_summary(certificate: &SecCertificate) -> Result<CFString, Error> {
+ let result = unsafe { SecCertificateCopySubjectSummary(certificate.as_concrete_TypeRef()) };
+ if result.is_null() {
+ return Err(error_here!(ErrorType::ExternalError));
+ }
+ Ok(unsafe { CFString::wrap_under_create_rule(result) })
+}
+
+fn sec_certificate_copy_data(certificate: &SecCertificate) -> Result<CFData, Error> {
+ let result = unsafe { SecCertificateCopyData(certificate.as_concrete_TypeRef()) };
+ if result.is_null() {
+ return Err(error_here!(ErrorType::ExternalError));
+ }
+ Ok(unsafe { CFData::wrap_under_create_rule(result) })
+}
+
+fn sec_identity_copy_private_key(identity: &SecIdentity) -> Result<SecKey, Error> {
+ let mut key = std::ptr::null();
+ let status = unsafe { SecIdentityCopyPrivateKey(identity.as_concrete_TypeRef(), &mut key) };
+ if status != errSecSuccess {
+ return Err(error_here!(ErrorType::ExternalError));
+ }
+ if key.is_null() {
+ return Err(error_here!(ErrorType::ExternalError));
+ }
+ Ok(unsafe { SecKey::wrap_under_create_rule(key) })
+}
+
+pub struct Cert {
+ class: Vec<u8>,
+ token: Vec<u8>,
+ id: Vec<u8>,
+ label: Vec<u8>,
+ value: Vec<u8>,
+ issuer: Vec<u8>,
+ serial_number: Vec<u8>,
+ subject: Vec<u8>,
+}
+
+impl Cert {
+ fn new_from_identity(identity: &SecIdentity) -> Result<Cert, Error> {
+ let certificate = sec_identity_copy_certificate(identity)?;
+ Cert::new_from_certificate(&certificate)
+ }
+
+ fn new_from_certificate(certificate: &SecCertificate) -> Result<Cert, Error> {
+ let label = sec_certificate_copy_subject_summary(certificate)?;
+ let der = sec_certificate_copy_data(certificate)?;
+ let der = der.bytes().to_vec();
+ let id = Sha256::digest(&der).to_vec();
+ let (serial_number, issuer, subject) = read_encoded_certificate_identifiers(&der)?;
+ Ok(Cert {
+ class: serialize_uint(CKO_CERTIFICATE)?,
+ token: serialize_uint(CK_TRUE)?,
+ id,
+ label: label.to_string().into_bytes(),
+ value: der,
+ issuer,
+ serial_number,
+ subject,
+ })
+ }
+
+ fn class(&self) -> &[u8] {
+ &self.class
+ }
+
+ fn token(&self) -> &[u8] {
+ &self.token
+ }
+
+ fn id(&self) -> &[u8] {
+ &self.id
+ }
+
+ fn label(&self) -> &[u8] {
+ &self.label
+ }
+
+ fn value(&self) -> &[u8] {
+ &self.value
+ }
+
+ fn issuer(&self) -> &[u8] {
+ &self.issuer
+ }
+
+ fn serial_number(&self) -> &[u8] {
+ &self.serial_number
+ }
+
+ fn subject(&self) -> &[u8] {
+ &self.subject
+ }
+}
+
+impl CryptokiObject for Cert {
+ fn matches(&self, slot_type: SlotType, attrs: &[(CK_ATTRIBUTE_TYPE, Vec<u8>)]) -> bool {
+ // The modern/legacy slot distinction in theory enables differentiation
+ // between keys that are from modules that can use modern cryptography
+ // (namely EC keys and RSA-PSS signatures) and those that cannot.
+ // However, the function that would enable this
+ // (SecKeyIsAlgorithmSupported) causes a password dialog to appear on
+ // our test machines, so this backend pretends that everything supports
+ // modern crypto for now.
+ if slot_type != SlotType::Modern {
+ return false;
+ }
+ for (attr_type, attr_value) in attrs {
+ let comparison = match *attr_type {
+ CKA_CLASS => self.class(),
+ CKA_TOKEN => self.token(),
+ CKA_LABEL => self.label(),
+ CKA_ID => self.id(),
+ CKA_VALUE => self.value(),
+ CKA_ISSUER => self.issuer(),
+ CKA_SERIAL_NUMBER => self.serial_number(),
+ CKA_SUBJECT => self.subject(),
+ _ => return false,
+ };
+ if attr_value.as_slice() != comparison {
+ return false;
+ }
+ }
+ true
+ }
+
+ fn get_attribute(&self, attribute: CK_ATTRIBUTE_TYPE) -> Option<&[u8]> {
+ let result = match attribute {
+ CKA_CLASS => self.class(),
+ CKA_TOKEN => self.token(),
+ CKA_LABEL => self.label(),
+ CKA_ID => self.id(),
+ CKA_VALUE => self.value(),
+ CKA_ISSUER => self.issuer(),
+ CKA_SERIAL_NUMBER => self.serial_number(),
+ CKA_SUBJECT => self.subject(),
+ _ => return None,
+ };
+ Some(result)
+ }
+}
+
+#[allow(clippy::upper_case_acronyms)]
+#[derive(Clone, Copy, Debug)]
+pub enum KeyType {
+ EC(usize),
+ RSA,
+}
+
+#[allow(clippy::upper_case_acronyms)]
+enum SignParams<'a> {
+ EC(CFString, &'a [u8]),
+ RSA(CFString, &'a [u8]),
+}
+
+impl<'a> SignParams<'a> {
+ fn new(
+ key_type: KeyType,
+ data: &'a [u8],
+ params: &Option<CK_RSA_PKCS_PSS_PARAMS>,
+ ) -> Result<SignParams<'a>, Error> {
+ match key_type {
+ KeyType::EC(_) => SignParams::new_ec_params(data),
+ KeyType::RSA => SignParams::new_rsa_params(params, data),
+ }
+ }
+
+ fn new_ec_params(data: &'a [u8]) -> Result<SignParams<'a>, Error> {
+ let algorithm = unsafe {
+ CFString::wrap_under_get_rule(match data.len() {
+ 20 => kSecKeyAlgorithmECDSASignatureDigestX962SHA1,
+ 32 => kSecKeyAlgorithmECDSASignatureDigestX962SHA256,
+ 48 => kSecKeyAlgorithmECDSASignatureDigestX962SHA384,
+ 64 => kSecKeyAlgorithmECDSASignatureDigestX962SHA512,
+ _ => {
+ return Err(error_here!(ErrorType::UnsupportedInput));
+ }
+ })
+ };
+ Ok(SignParams::EC(algorithm, data))
+ }
+
+ fn new_rsa_params(
+ params: &Option<CK_RSA_PKCS_PSS_PARAMS>,
+ data: &'a [u8],
+ ) -> Result<SignParams<'a>, Error> {
+ if let Some(pss_params) = params {
+ let algorithm = {
+ let algorithm_id = match pss_params.hashAlg {
+ CKM_SHA_1 => SecStringConstant::SecKeyAlgorithmRSASignatureDigestPSSSHA1,
+ CKM_SHA256 => SecStringConstant::SecKeyAlgorithmRSASignatureDigestPSSSHA256,
+ CKM_SHA384 => SecStringConstant::SecKeyAlgorithmRSASignatureDigestPSSSHA384,
+ CKM_SHA512 => SecStringConstant::SecKeyAlgorithmRSASignatureDigestPSSSHA512,
+ _ => {
+ return Err(error_here!(ErrorType::UnsupportedInput));
+ }
+ };
+ SECURITY_FRAMEWORK.get_sec_string_constant(algorithm_id)?
+ };
+ return Ok(SignParams::RSA(algorithm, data));
+ }
+
+ // Handle the case where this is a TLS 1.0 MD5/SHA1 hash.
+ if data.len() == 36 {
+ let algorithm = unsafe {
+ CFString::wrap_under_get_rule(kSecKeyAlgorithmRSASignatureDigestPKCS1v15Raw)
+ };
+ return Ok(SignParams::RSA(algorithm, data));
+ }
+ // Otherwise, `data` should be a DigestInfo.
+ let (digest_oid, hash) = read_digest_info(data)?;
+ let algorithm = unsafe {
+ CFString::wrap_under_create_rule(match digest_oid {
+ OID_BYTES_SHA_256 => kSecKeyAlgorithmRSASignatureDigestPKCS1v15SHA256,
+ OID_BYTES_SHA_384 => kSecKeyAlgorithmRSASignatureDigestPKCS1v15SHA384,
+ OID_BYTES_SHA_512 => kSecKeyAlgorithmRSASignatureDigestPKCS1v15SHA512,
+ OID_BYTES_SHA_1 => kSecKeyAlgorithmRSASignatureDigestPKCS1v15SHA1,
+ _ => return Err(error_here!(ErrorType::UnsupportedInput)),
+ })
+ };
+
+ Ok(SignParams::RSA(algorithm, hash))
+ }
+
+ fn get_algorithm(&self) -> SecKeyAlgorithm {
+ match self {
+ SignParams::EC(algorithm, _) => algorithm.as_concrete_TypeRef(),
+ SignParams::RSA(algorithm, _) => algorithm.as_concrete_TypeRef(),
+ }
+ }
+
+ fn get_data_to_sign(&self) -> &'a [u8] {
+ match self {
+ SignParams::EC(_, data_to_sign) => data_to_sign,
+ SignParams::RSA(_, data_to_sign) => data_to_sign,
+ }
+ }
+}
+
+pub struct Key {
+ identity: SecIdentity,
+ class: Vec<u8>,
+ token: Vec<u8>,
+ id: Vec<u8>,
+ private: Vec<u8>,
+ key_type: Vec<u8>,
+ modulus: Option<Vec<u8>>,
+ ec_params: Option<Vec<u8>>,
+ key_type_enum: KeyType,
+ key_handle: Option<SecKey>,
+}
+
+impl Key {
+ fn new(identity: &SecIdentity) -> Result<Key, Error> {
+ let certificate = sec_identity_copy_certificate(identity)?;
+ let der = sec_certificate_copy_data(&certificate)?;
+ let id = Sha256::digest(der.bytes()).to_vec();
+ let key = SECURITY_FRAMEWORK.sec_certificate_copy_key(&certificate)?;
+ let key_type: CFString = get_key_attribute(&key, unsafe { kSecAttrKeyType })?;
+ let key_size_in_bits: CFNumber = get_key_attribute(&key, unsafe { kSecAttrKeySizeInBits })?;
+ let mut modulus = None;
+ let mut ec_params = None;
+ let sec_attr_key_type_ec =
+ unsafe { CFString::wrap_under_create_rule(kSecAttrKeyTypeECSECPrimeRandom) };
+ let (key_type_enum, key_type_attribute) =
+ if key_type.as_concrete_TypeRef() == unsafe { kSecAttrKeyTypeRSA } {
+ let public_key = sec_key_copy_external_representation(&key)?;
+ let modulus_value = read_rsa_modulus(public_key.bytes())?;
+ modulus = Some(modulus_value);
+ (KeyType::RSA, CKK_RSA)
+ } else if key_type == sec_attr_key_type_ec {
+ // Assume all EC keys are secp256r1, secp384r1, or secp521r1. This
+ // is wrong, but the API doesn't seem to give us a way to determine
+ // which curve this key is on.
+ // This might not matter in practice, because it seems all NSS uses
+ // this for is to get the signature size.
+ let key_size_in_bits = match key_size_in_bits.to_i64() {
+ Some(value) => value,
+ None => return Err(error_here!(ErrorType::ValueTooLarge)),
+ };
+ match key_size_in_bits {
+ 256 => ec_params = Some(ENCODED_OID_BYTES_SECP256R1.to_vec()),
+ 384 => ec_params = Some(ENCODED_OID_BYTES_SECP384R1.to_vec()),
+ 521 => ec_params = Some(ENCODED_OID_BYTES_SECP521R1.to_vec()),
+ _ => return Err(error_here!(ErrorType::UnsupportedInput)),
+ }
+ let coordinate_width = (key_size_in_bits as usize + 7) / 8;
+ (KeyType::EC(coordinate_width), CKK_EC)
+ } else {
+ return Err(error_here!(ErrorType::LibraryFailure));
+ };
+
+ Ok(Key {
+ identity: identity.clone(),
+ class: serialize_uint(CKO_PRIVATE_KEY)?,
+ token: serialize_uint(CK_TRUE)?,
+ id,
+ private: serialize_uint(CK_TRUE)?,
+ key_type: serialize_uint(key_type_attribute)?,
+ modulus,
+ ec_params,
+ key_type_enum,
+ key_handle: None,
+ })
+ }
+
+ fn class(&self) -> &[u8] {
+ &self.class
+ }
+
+ fn token(&self) -> &[u8] {
+ &self.token
+ }
+
+ fn id(&self) -> &[u8] {
+ &self.id
+ }
+
+ fn private(&self) -> &[u8] {
+ &self.private
+ }
+
+ fn key_type(&self) -> &[u8] {
+ &self.key_type
+ }
+
+ fn modulus(&self) -> Option<&[u8]> {
+ match &self.modulus {
+ Some(modulus) => Some(modulus.as_slice()),
+ None => None,
+ }
+ }
+
+ fn ec_params(&self) -> Option<&[u8]> {
+ match &self.ec_params {
+ Some(ec_params) => Some(ec_params.as_slice()),
+ None => None,
+ }
+ }
+
+ fn sign_internal(
+ &mut self,
+ data: &[u8],
+ params: &Option<CK_RSA_PKCS_PSS_PARAMS>,
+ ) -> Result<Vec<u8>, Error> {
+ // If this key hasn't been used for signing yet, there won't be a cached key handle. Obtain
+ // and cache it if this is the case. Doing so can cause the underlying implementation to
+ // show an authentication or pin prompt to the user. Caching the handle can avoid causing
+ // multiple prompts to be displayed in some cases.
+ if self.key_handle.is_none() {
+ let _ = self
+ .key_handle
+ .replace(sec_identity_copy_private_key(&self.identity)?);
+ }
+ let key = match &self.key_handle {
+ Some(key) => key,
+ None => return Err(error_here!(ErrorType::LibraryFailure)),
+ };
+ let sign_params = SignParams::new(self.key_type_enum, data, params)?;
+ let signing_algorithm = sign_params.get_algorithm();
+ let data_to_sign = CFData::from_buffer(sign_params.get_data_to_sign());
+ let signature = sec_key_create_signature(key, signing_algorithm, &data_to_sign)?;
+ let signature_value = match self.key_type_enum {
+ KeyType::EC(coordinate_width) => {
+ // We need to convert the DER Ecdsa-Sig-Value to the
+ // concatenation of r and s, the coordinates of the point on
+ // the curve. r and s must be 0-padded to be coordinate_width
+ // total bytes.
+ let (r, s) = read_ec_sig_point(signature.bytes())?;
+ if r.len() > coordinate_width || s.len() > coordinate_width {
+ return Err(error_here!(ErrorType::InvalidInput));
+ }
+ let mut signature_value = Vec::with_capacity(2 * coordinate_width);
+ let r_padding = vec![0; coordinate_width - r.len()];
+ signature_value.extend(r_padding);
+ signature_value.extend_from_slice(r);
+ let s_padding = vec![0; coordinate_width - s.len()];
+ signature_value.extend(s_padding);
+ signature_value.extend_from_slice(s);
+ signature_value
+ }
+ KeyType::RSA => signature.bytes().to_vec(),
+ };
+ Ok(signature_value)
+ }
+}
+
+impl CryptokiObject for Key {
+ fn matches(&self, slot_type: SlotType, attrs: &[(CK_ATTRIBUTE_TYPE, Vec<u8>)]) -> bool {
+ // The modern/legacy slot distinction in theory enables differentiation
+ // between keys that are from modules that can use modern cryptography
+ // (namely EC keys and RSA-PSS signatures) and those that cannot.
+ // However, the function that would enable this
+ // (SecKeyIsAlgorithmSupported) causes a password dialog to appear on
+ // our test machines, so this backend pretends that everything supports
+ // modern crypto for now.
+ if slot_type != SlotType::Modern {
+ return false;
+ }
+ for (attr_type, attr_value) in attrs {
+ let comparison = match *attr_type {
+ CKA_CLASS => self.class(),
+ CKA_TOKEN => self.token(),
+ CKA_ID => self.id(),
+ CKA_PRIVATE => self.private(),
+ CKA_KEY_TYPE => self.key_type(),
+ CKA_MODULUS => {
+ if let Some(modulus) = self.modulus() {
+ modulus
+ } else {
+ return false;
+ }
+ }
+ CKA_EC_PARAMS => {
+ if let Some(ec_params) = self.ec_params() {
+ ec_params
+ } else {
+ return false;
+ }
+ }
+ _ => return false,
+ };
+ if attr_value.as_slice() != comparison {
+ return false;
+ }
+ }
+ true
+ }
+
+ fn get_attribute(&self, attribute: CK_ATTRIBUTE_TYPE) -> Option<&[u8]> {
+ match attribute {
+ CKA_CLASS => Some(self.class()),
+ CKA_TOKEN => Some(self.token()),
+ CKA_ID => Some(self.id()),
+ CKA_PRIVATE => Some(self.private()),
+ CKA_KEY_TYPE => Some(self.key_type()),
+ CKA_MODULUS => self.modulus(),
+ CKA_EC_PARAMS => self.ec_params(),
+ _ => None,
+ }
+ }
+}
+
+impl Sign for Key {
+ fn get_signature_length(
+ &mut self,
+ data: &[u8],
+ params: &Option<CK_RSA_PKCS_PSS_PARAMS>,
+ ) -> Result<usize, Error> {
+ // Unfortunately we don't have a way of getting the length of a signature without creating
+ // one.
+ let dummy_signature_bytes = self.sign(data, params)?;
+ Ok(dummy_signature_bytes.len())
+ }
+
+ // The input data is a hash. What algorithm we use depends on the size of the hash.
+ fn sign(
+ &mut self,
+ data: &[u8],
+ params: &Option<CK_RSA_PKCS_PSS_PARAMS>,
+ ) -> Result<Vec<u8>, Error> {
+ let result = self.sign_internal(data, params);
+ if result.is_ok() {
+ return result;
+ }
+ // Some devices appear to not work well when the key handle is held for too long or if a
+ // card is inserted/removed while Firefox is running. Try refreshing the key handle.
+ let _ = self.key_handle.take();
+ self.sign_internal(data, params)
+ }
+}
+
+fn get_key_attribute<T: TCFType + Clone>(key: &SecKey, attr: CFStringRef) -> Result<T, Error> {
+ let attributes: CFDictionary<CFString, T> = sec_key_copy_attributes(key);
+ match attributes.find(attr as *const _) {
+ Some(value) => Ok((*value).clone()),
+ None => Err(error_here!(ErrorType::ExternalError)),
+ }
+}
+
+// Given a SecIdentity, attempts to build as much of a path to a trust anchor as possible, gathers
+// the CA certificates from that path, and returns them. The purpose of this function is not to
+// validate the given certificate but to find CA certificates that gecko may need to do path
+// building when filtering client certificates according to the acceptable CA list sent by the
+// server during client authentication.
+fn get_issuers(identity: &SecIdentity) -> Result<Vec<SecCertificate>, Error> {
+ let certificate = sec_identity_copy_certificate(identity)?;
+ let policy = unsafe { SecPolicyCreateSSL(false, std::ptr::null()) };
+ if policy.is_null() {
+ return Err(error_here!(ErrorType::ExternalError));
+ }
+ let policy = unsafe { SecPolicy::wrap_under_create_rule(policy) };
+ let mut trust = std::ptr::null();
+ // Each of SecTrustCreateWithCertificates' input arguments can be either single items or an
+ // array of items. Since we only want to specify one of each, we directly specify the arguments.
+ let status = unsafe {
+ SecTrustCreateWithCertificates(
+ certificate.as_concrete_TypeRef(),
+ policy.as_concrete_TypeRef(),
+ &mut trust,
+ )
+ };
+ if status != errSecSuccess {
+ return Err(error_here!(ErrorType::ExternalError));
+ }
+ if trust.is_null() {
+ return Err(error_here!(ErrorType::ExternalError));
+ }
+ let trust = unsafe { SecTrust::wrap_under_create_rule(trust) };
+ // Disable AIA fetching so that SecTrustEvaluateWithError doesn't result in network I/O.
+ let status = unsafe { SecTrustSetNetworkFetchAllowed(trust.as_concrete_TypeRef(), 0) };
+ if status != errSecSuccess {
+ return Err(error_here!(ErrorType::ExternalError));
+ }
+ // We ignore the return value here because we don't care if the certificate is trusted or not -
+ // we're only doing this to build its issuer chain as much as possible.
+ let _ = SECURITY_FRAMEWORK.sec_trust_evaluate_with_error(&trust)?;
+ let certificate_count = unsafe { SecTrustGetCertificateCount(trust.as_concrete_TypeRef()) };
+ let mut certificates = Vec::with_capacity(
+ certificate_count
+ .try_into()
+ .map_err(|_| error_here!(ErrorType::ValueTooLarge))?,
+ );
+ for i in 1..certificate_count {
+ let certificate = unsafe { SecTrustGetCertificateAtIndex(trust.as_concrete_TypeRef(), i) };
+ if certificate.is_null() {
+ error!("SecTrustGetCertificateAtIndex returned null certificate?");
+ continue;
+ }
+ let certificate = unsafe { SecCertificate::wrap_under_get_rule(certificate) };
+ certificates.push(certificate);
+ }
+ Ok(certificates)
+}
+
+pub struct Backend {}
+
+impl ClientCertsBackend for Backend {
+ type Cert = Cert;
+ type Key = Key;
+
+ fn find_objects(&self) -> Result<(Vec<Cert>, Vec<Key>), Error> {
+ let mut certs = Vec::new();
+ let mut keys = Vec::new();
+ let identities = unsafe {
+ let class_key = CFString::wrap_under_get_rule(kSecClass);
+ let class_value = CFString::wrap_under_get_rule(kSecClassIdentity);
+ let return_ref_key = CFString::wrap_under_get_rule(kSecReturnRef);
+ let return_ref_value = CFBoolean::wrap_under_get_rule(kCFBooleanTrue);
+ let match_key = CFString::wrap_under_get_rule(kSecMatchLimit);
+ let match_value = CFString::wrap_under_get_rule(kSecMatchLimitAll);
+ let vals = vec![
+ (class_key.as_CFType(), class_value.as_CFType()),
+ (return_ref_key.as_CFType(), return_ref_value.as_CFType()),
+ (match_key.as_CFType(), match_value.as_CFType()),
+ ];
+ let dict = CFDictionary::from_CFType_pairs(&vals);
+ let mut result = std::ptr::null();
+ let status = SecItemCopyMatching(dict.as_CFTypeRef() as CFDictionaryRef, &mut result);
+ if status == errSecItemNotFound {
+ return Ok((certs, keys));
+ }
+ if status != errSecSuccess {
+ return Err(error_here!(ErrorType::ExternalError, status.to_string()));
+ }
+ if result.is_null() {
+ return Err(error_here!(ErrorType::ExternalError));
+ }
+ CFArray::<SecIdentityRef>::wrap_under_create_rule(result as CFArrayRef)
+ };
+ for identity in identities.get_all_values().iter() {
+ let identity = unsafe { SecIdentity::wrap_under_get_rule(*identity as SecIdentityRef) };
+ let cert = Cert::new_from_identity(&identity);
+ let key = Key::new(&identity);
+ if let (Ok(cert), Ok(key)) = (cert, key) {
+ certs.push(cert);
+ keys.push(key);
+ } else {
+ continue;
+ }
+ if let Ok(issuers) = get_issuers(&identity) {
+ for issuer in issuers {
+ if let Ok(cert) = Cert::new_from_certificate(&issuer) {
+ certs.push(cert);
+ }
+ }
+ }
+ }
+ Ok((certs, keys))
+ }
+}
diff --git a/security/manager/ssl/osclientcerts/src/backend_windows.rs b/security/manager/ssl/osclientcerts/src/backend_windows.rs
new file mode 100644
index 0000000000..2a80ff8354
--- /dev/null
+++ b/security/manager/ssl/osclientcerts/src/backend_windows.rs
@@ -0,0 +1,914 @@
+/* -*- Mode: rust; rust-indent-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#![allow(non_camel_case_types)]
+
+use pkcs11_bindings::*;
+use rsclientcerts::error::{Error, ErrorType};
+use rsclientcerts::manager::{ClientCertsBackend, CryptokiObject, Sign, SlotType};
+use rsclientcerts::util::*;
+use sha2::{Digest, Sha256};
+use std::convert::TryInto;
+use std::ffi::{c_void, CStr, CString};
+use std::ops::Deref;
+use std::slice;
+use winapi::shared::bcrypt::*;
+use winapi::shared::minwindef::{DWORD, PBYTE};
+use winapi::um::errhandlingapi::GetLastError;
+use winapi::um::ncrypt::*;
+use winapi::um::wincrypt::{HCRYPTHASH, HCRYPTPROV, *};
+
+// winapi has some support for ncrypt.h, but not for this function.
+extern "system" {
+ fn NCryptSignHash(
+ hKey: NCRYPT_KEY_HANDLE,
+ pPaddingInfo: *mut c_void,
+ pbHashValue: PBYTE,
+ cbHashValue: DWORD,
+ pbSignature: PBYTE,
+ cbSignature: DWORD,
+ pcbResult: *mut DWORD,
+ dwFlags: DWORD,
+ ) -> SECURITY_STATUS;
+}
+
+/// Given a `CERT_INFO`, tries to return the bytes of the subject distinguished name as formatted by
+/// `CertNameToStrA` using the flag `CERT_SIMPLE_NAME_STR`. This is used as the label for the
+/// certificate.
+fn get_cert_subject_dn(cert_info: &CERT_INFO) -> Result<Vec<u8>, Error> {
+ let mut cert_info_subject = cert_info.Subject;
+ let subject_dn_len = unsafe {
+ CertNameToStrA(
+ X509_ASN_ENCODING,
+ &mut cert_info_subject,
+ CERT_SIMPLE_NAME_STR,
+ std::ptr::null_mut(),
+ 0,
+ )
+ };
+ // subject_dn_len includes the terminating null byte.
+ let mut subject_dn_string_bytes: Vec<u8> = vec![0; subject_dn_len as usize];
+ let subject_dn_len = unsafe {
+ CertNameToStrA(
+ X509_ASN_ENCODING,
+ &mut cert_info_subject,
+ CERT_SIMPLE_NAME_STR,
+ subject_dn_string_bytes.as_mut_ptr() as *mut i8,
+ subject_dn_string_bytes
+ .len()
+ .try_into()
+ .map_err(|_| error_here!(ErrorType::ValueTooLarge))?,
+ )
+ };
+ if subject_dn_len as usize != subject_dn_string_bytes.len() {
+ return Err(error_here!(ErrorType::ExternalError));
+ }
+ Ok(subject_dn_string_bytes)
+}
+
+/// Represents a certificate for which there exists a corresponding private key.
+pub struct Cert {
+ /// PKCS #11 object class. Will be `CKO_CERTIFICATE`.
+ class: Vec<u8>,
+ /// Whether or not this is on a token. Will be `CK_TRUE`.
+ token: Vec<u8>,
+ /// An identifier unique to this certificate. Must be the same as the ID for the private key.
+ id: Vec<u8>,
+ /// The bytes of a human-readable label for this certificate. Will be the subject DN.
+ label: Vec<u8>,
+ /// The DER bytes of the certificate.
+ value: Vec<u8>,
+ /// The DER bytes of the issuer distinguished name of the certificate.
+ issuer: Vec<u8>,
+ /// The DER bytes of the serial number of the certificate.
+ serial_number: Vec<u8>,
+ /// The DER bytes of the subject distinguished name of the certificate.
+ subject: Vec<u8>,
+ /// Which slot this certificate should be exposed on.
+ slot_type: SlotType,
+}
+
+impl Cert {
+ fn new(cert_context: PCCERT_CONTEXT) -> Result<Cert, Error> {
+ let cert = unsafe { &*cert_context };
+ let cert_info = unsafe { &*cert.pCertInfo };
+ let value =
+ unsafe { slice::from_raw_parts(cert.pbCertEncoded, cert.cbCertEncoded as usize) };
+ let value = value.to_vec();
+ let id = Sha256::digest(&value).to_vec();
+ let label = get_cert_subject_dn(cert_info)?;
+ let (serial_number, issuer, subject) = read_encoded_certificate_identifiers(&value)?;
+ Ok(Cert {
+ class: serialize_uint(CKO_CERTIFICATE)?,
+ token: serialize_uint(CK_TRUE)?,
+ id,
+ label,
+ value,
+ issuer,
+ serial_number,
+ subject,
+ slot_type: SlotType::Modern,
+ })
+ }
+
+ fn class(&self) -> &[u8] {
+ &self.class
+ }
+
+ fn token(&self) -> &[u8] {
+ &self.token
+ }
+
+ fn id(&self) -> &[u8] {
+ &self.id
+ }
+
+ fn label(&self) -> &[u8] {
+ &self.label
+ }
+
+ fn value(&self) -> &[u8] {
+ &self.value
+ }
+
+ fn issuer(&self) -> &[u8] {
+ &self.issuer
+ }
+
+ fn serial_number(&self) -> &[u8] {
+ &self.serial_number
+ }
+
+ fn subject(&self) -> &[u8] {
+ &self.subject
+ }
+}
+
+impl CryptokiObject for Cert {
+ fn matches(&self, slot_type: SlotType, attrs: &[(CK_ATTRIBUTE_TYPE, Vec<u8>)]) -> bool {
+ if slot_type != self.slot_type {
+ return false;
+ }
+ for (attr_type, attr_value) in attrs {
+ let comparison = match *attr_type {
+ CKA_CLASS => self.class(),
+ CKA_TOKEN => self.token(),
+ CKA_LABEL => self.label(),
+ CKA_ID => self.id(),
+ CKA_VALUE => self.value(),
+ CKA_ISSUER => self.issuer(),
+ CKA_SERIAL_NUMBER => self.serial_number(),
+ CKA_SUBJECT => self.subject(),
+ _ => return false,
+ };
+ if attr_value.as_slice() != comparison {
+ return false;
+ }
+ }
+ true
+ }
+
+ fn get_attribute(&self, attribute: CK_ATTRIBUTE_TYPE) -> Option<&[u8]> {
+ let result = match attribute {
+ CKA_CLASS => self.class(),
+ CKA_TOKEN => self.token(),
+ CKA_LABEL => self.label(),
+ CKA_ID => self.id(),
+ CKA_VALUE => self.value(),
+ CKA_ISSUER => self.issuer(),
+ CKA_SERIAL_NUMBER => self.serial_number(),
+ CKA_SUBJECT => self.subject(),
+ _ => return None,
+ };
+ Some(result)
+ }
+}
+
+struct CertContext(PCCERT_CONTEXT);
+
+impl CertContext {
+ fn new(cert: PCCERT_CONTEXT) -> CertContext {
+ CertContext(unsafe { CertDuplicateCertificateContext(cert) })
+ }
+}
+
+impl Drop for CertContext {
+ fn drop(&mut self) {
+ unsafe {
+ CertFreeCertificateContext(self.0);
+ }
+ }
+}
+
+impl Deref for CertContext {
+ type Target = PCCERT_CONTEXT;
+
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+}
+
+enum KeyHandle {
+ NCrypt(NCRYPT_KEY_HANDLE),
+ CryptoAPI(HCRYPTPROV, DWORD),
+}
+
+impl KeyHandle {
+ fn from_cert(cert: &CertContext) -> Result<KeyHandle, Error> {
+ let mut key_handle = 0;
+ let mut key_spec = 0;
+ let mut must_free = 0;
+ unsafe {
+ if CryptAcquireCertificatePrivateKey(
+ **cert,
+ CRYPT_ACQUIRE_PREFER_NCRYPT_KEY_FLAG,
+ std::ptr::null_mut(),
+ &mut key_handle,
+ &mut key_spec,
+ &mut must_free,
+ ) != 1
+ {
+ return Err(error_here!(
+ ErrorType::ExternalError,
+ GetLastError().to_string()
+ ));
+ }
+ }
+ if must_free == 0 {
+ return Err(error_here!(ErrorType::ExternalError));
+ }
+ if key_spec == CERT_NCRYPT_KEY_SPEC {
+ Ok(KeyHandle::NCrypt(key_handle as NCRYPT_KEY_HANDLE))
+ } else {
+ Ok(KeyHandle::CryptoAPI(key_handle as HCRYPTPROV, key_spec))
+ }
+ }
+
+ fn sign(
+ &self,
+ data: &[u8],
+ params: &Option<CK_RSA_PKCS_PSS_PARAMS>,
+ do_signature: bool,
+ key_type: KeyType,
+ ) -> Result<Vec<u8>, Error> {
+ match &self {
+ KeyHandle::NCrypt(ncrypt_handle) => {
+ sign_ncrypt(ncrypt_handle, data, params, do_signature, key_type)
+ }
+ KeyHandle::CryptoAPI(hcryptprov, key_spec) => {
+ sign_cryptoapi(hcryptprov, key_spec, data, params, do_signature)
+ }
+ }
+ }
+}
+
+impl Drop for KeyHandle {
+ fn drop(&mut self) {
+ match self {
+ KeyHandle::NCrypt(ncrypt_handle) => unsafe {
+ let _ = NCryptFreeObject(*ncrypt_handle);
+ },
+ KeyHandle::CryptoAPI(hcryptprov, _) => unsafe {
+ let _ = CryptReleaseContext(*hcryptprov, 0);
+ },
+ }
+ }
+}
+
+fn sign_ncrypt(
+ ncrypt_handle: &NCRYPT_KEY_HANDLE,
+ data: &[u8],
+ params: &Option<CK_RSA_PKCS_PSS_PARAMS>,
+ do_signature: bool,
+ key_type: KeyType,
+) -> Result<Vec<u8>, Error> {
+ let mut sign_params = SignParams::new(key_type, params)?;
+ let params_ptr = sign_params.params_ptr();
+ let flags = sign_params.flags();
+ let mut data = data.to_vec();
+ let mut signature_len = 0;
+ // We call NCryptSignHash twice: the first time to get the size of the buffer we need to
+ // allocate and then again to actually sign the data, if `do_signature` is `true`.
+ let status = unsafe {
+ NCryptSignHash(
+ *ncrypt_handle,
+ params_ptr,
+ data.as_mut_ptr(),
+ data.len()
+ .try_into()
+ .map_err(|_| error_here!(ErrorType::ValueTooLarge))?,
+ std::ptr::null_mut(),
+ 0,
+ &mut signature_len,
+ flags,
+ )
+ };
+ // 0 is "ERROR_SUCCESS" (but "ERROR_SUCCESS" is unsigned, whereas SECURITY_STATUS is signed)
+ if status != 0 {
+ return Err(error_here!(ErrorType::ExternalError, status.to_string()));
+ }
+ let mut signature = vec![0; signature_len as usize];
+ if !do_signature {
+ return Ok(signature);
+ }
+ let mut final_signature_len = signature_len;
+ let status = unsafe {
+ NCryptSignHash(
+ *ncrypt_handle,
+ params_ptr,
+ data.as_mut_ptr(),
+ data.len()
+ .try_into()
+ .map_err(|_| error_here!(ErrorType::ValueTooLarge))?,
+ signature.as_mut_ptr(),
+ signature_len,
+ &mut final_signature_len,
+ flags,
+ )
+ };
+ if status != 0 {
+ return Err(error_here!(ErrorType::ExternalError, status.to_string()));
+ }
+ if final_signature_len != signature_len {
+ return Err(error_here!(ErrorType::ExternalError));
+ }
+ Ok(signature)
+}
+
+fn sign_cryptoapi(
+ hcryptprov: &HCRYPTPROV,
+ key_spec: &DWORD,
+ data: &[u8],
+ params: &Option<CK_RSA_PKCS_PSS_PARAMS>,
+ do_signature: bool,
+) -> Result<Vec<u8>, Error> {
+ if params.is_some() {
+ return Err(error_here!(ErrorType::LibraryFailure));
+ }
+ // data will be an encoded DigestInfo, which specifies the hash algorithm and bytes of the hash
+ // to sign. However, CryptoAPI requires directly specifying the bytes of the hash, so it must
+ // be extracted first.
+ let (_, hash_bytes) = read_digest_info(data)?;
+ let hash = HCryptHash::new(hcryptprov, hash_bytes)?;
+ let mut signature_len = 0;
+ if unsafe {
+ CryptSignHashW(
+ *hash,
+ *key_spec,
+ std::ptr::null_mut(),
+ 0,
+ std::ptr::null_mut(),
+ &mut signature_len,
+ )
+ } != 1
+ {
+ return Err(error_here!(
+ ErrorType::ExternalError,
+ unsafe { GetLastError() }.to_string()
+ ));
+ }
+ let mut signature = vec![0; signature_len as usize];
+ if !do_signature {
+ return Ok(signature);
+ }
+ let mut final_signature_len = signature_len;
+ if unsafe {
+ CryptSignHashW(
+ *hash,
+ *key_spec,
+ std::ptr::null_mut(),
+ 0,
+ signature.as_mut_ptr(),
+ &mut final_signature_len,
+ )
+ } != 1
+ {
+ return Err(error_here!(
+ ErrorType::ExternalError,
+ unsafe { GetLastError() }.to_string()
+ ));
+ }
+ if final_signature_len != signature_len {
+ return Err(error_here!(ErrorType::ExternalError));
+ }
+ // CryptoAPI returns the signature with the most significant byte last (little-endian),
+ // whereas PKCS#11 expects the most significant byte first (big-endian).
+ signature.reverse();
+ Ok(signature)
+}
+
+struct HCryptHash(HCRYPTHASH);
+
+impl HCryptHash {
+ fn new(hcryptprov: &HCRYPTPROV, hash_bytes: &[u8]) -> Result<HCryptHash, Error> {
+ let alg = match hash_bytes.len() {
+ 20 => CALG_SHA1,
+ 32 => CALG_SHA_256,
+ 48 => CALG_SHA_384,
+ 64 => CALG_SHA_512,
+ _ => {
+ return Err(error_here!(ErrorType::UnsupportedInput));
+ }
+ };
+ let mut hash: HCRYPTHASH = 0;
+ if unsafe { CryptCreateHash(*hcryptprov, alg, 0, 0, &mut hash) } != 1 {
+ return Err(error_here!(
+ ErrorType::ExternalError,
+ unsafe { GetLastError() }.to_string()
+ ));
+ }
+ if unsafe { CryptSetHashParam(hash, HP_HASHVAL, hash_bytes.as_ptr(), 0) } != 1 {
+ return Err(error_here!(
+ ErrorType::ExternalError,
+ unsafe { GetLastError() }.to_string()
+ ));
+ }
+ Ok(HCryptHash(hash))
+ }
+}
+
+impl Drop for HCryptHash {
+ fn drop(&mut self) {
+ unsafe {
+ CryptDestroyHash(self.0);
+ }
+ }
+}
+
+impl Deref for HCryptHash {
+ type Target = HCRYPTHASH;
+
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+}
+
+// In some cases, the ncrypt API takes a pointer to a null-terminated wide-character string as a way
+// of specifying an algorithm. The "right" way to do this would be to take the corresponding
+// &'static str constant provided by the winapi crate, create an OsString from it, encode it as wide
+// characters, and collect it into a Vec<u16>. However, since the implementation that provides this
+// functionality isn't constant, we would have to manage the memory this creates and uses. Since
+// rust structures generally can't be self-referrential, this memory would have to live elsewhere,
+// and the nice abstractions we've created for this implementation start to break down. It's much
+// simpler to hard-code the identifiers we support, since there are only four of them.
+// The following arrays represent the identifiers "SHA1", "SHA256", "SHA384", and "SHA512",
+// respectively.
+const SHA1_ALGORITHM_STRING: &[u16] = &[83, 72, 65, 49, 0];
+const SHA256_ALGORITHM_STRING: &[u16] = &[83, 72, 65, 50, 53, 54, 0];
+const SHA384_ALGORITHM_STRING: &[u16] = &[83, 72, 65, 51, 56, 52, 0];
+const SHA512_ALGORITHM_STRING: &[u16] = &[83, 72, 65, 53, 49, 50, 0];
+
+enum SignParams {
+ EC,
+ RSA_PKCS1(BCRYPT_PKCS1_PADDING_INFO),
+ RSA_PSS(BCRYPT_PSS_PADDING_INFO),
+}
+
+impl SignParams {
+ fn new(
+ key_type: KeyType,
+ params: &Option<CK_RSA_PKCS_PSS_PARAMS>,
+ ) -> Result<SignParams, Error> {
+ // EC is easy, so handle that first.
+ match key_type {
+ KeyType::EC => return Ok(SignParams::EC),
+ KeyType::RSA => {}
+ }
+ // If `params` is `Some`, we're doing RSA-PSS. If it is `None`, we're doing RSA-PKCS1.
+ let pss_params = match params {
+ Some(pss_params) => pss_params,
+ None => {
+ // The hash algorithm should be encoded in the data to be signed, so we don't have to
+ // (and don't want to) specify a particular algorithm here.
+ return Ok(SignParams::RSA_PKCS1(BCRYPT_PKCS1_PADDING_INFO {
+ pszAlgId: std::ptr::null(),
+ }));
+ }
+ };
+ let algorithm_string = match pss_params.hashAlg {
+ CKM_SHA_1 => SHA1_ALGORITHM_STRING,
+ CKM_SHA256 => SHA256_ALGORITHM_STRING,
+ CKM_SHA384 => SHA384_ALGORITHM_STRING,
+ CKM_SHA512 => SHA512_ALGORITHM_STRING,
+ _ => {
+ return Err(error_here!(ErrorType::UnsupportedInput));
+ }
+ };
+ Ok(SignParams::RSA_PSS(BCRYPT_PSS_PADDING_INFO {
+ pszAlgId: algorithm_string.as_ptr(),
+ cbSalt: pss_params.sLen,
+ }))
+ }
+
+ fn params_ptr(&mut self) -> *mut std::ffi::c_void {
+ match self {
+ SignParams::EC => std::ptr::null_mut(),
+ SignParams::RSA_PKCS1(params) => {
+ params as *mut BCRYPT_PKCS1_PADDING_INFO as *mut std::ffi::c_void
+ }
+ SignParams::RSA_PSS(params) => {
+ params as *mut BCRYPT_PSS_PADDING_INFO as *mut std::ffi::c_void
+ }
+ }
+ }
+
+ fn flags(&self) -> u32 {
+ match *self {
+ SignParams::EC => 0,
+ SignParams::RSA_PKCS1(_) => NCRYPT_PAD_PKCS1_FLAG,
+ SignParams::RSA_PSS(_) => NCRYPT_PAD_PSS_FLAG,
+ }
+ }
+}
+
+/// A helper enum to identify a private key's type. We support EC and RSA.
+#[allow(clippy::upper_case_acronyms)]
+#[derive(Clone, Copy, Debug)]
+pub enum KeyType {
+ EC,
+ RSA,
+}
+
+/// Represents a private key for which there exists a corresponding certificate.
+pub struct Key {
+ /// A handle on the OS mechanism that represents the certificate for this key.
+ cert: CertContext,
+ /// PKCS #11 object class. Will be `CKO_PRIVATE_KEY`.
+ class: Vec<u8>,
+ /// Whether or not this is on a token. Will be `CK_TRUE`.
+ token: Vec<u8>,
+ /// An identifier unique to this key. Must be the same as the ID for the certificate.
+ id: Vec<u8>,
+ /// Whether or not this key is "private" (can it be exported?). Will be CK_TRUE (it can't be
+ /// exported).
+ private: Vec<u8>,
+ /// PKCS #11 key type. Will be `CKK_EC` for EC, and `CKK_RSA` for RSA.
+ key_type: Vec<u8>,
+ /// If this is an RSA key, this is the value of the modulus as an unsigned integer.
+ modulus: Option<Vec<u8>>,
+ /// If this is an EC key, this is the DER bytes of the OID identifying the curve the key is on.
+ ec_params: Option<Vec<u8>>,
+ /// An enum identifying this key's type.
+ key_type_enum: KeyType,
+ /// Which slot this key should be exposed on.
+ slot_type: SlotType,
+ /// A handle on the OS mechanism that represents this key.
+ key_handle: Option<KeyHandle>,
+}
+
+impl Key {
+ fn new(cert_context: PCCERT_CONTEXT) -> Result<Key, Error> {
+ let cert = unsafe { *cert_context };
+ let cert_der =
+ unsafe { slice::from_raw_parts(cert.pbCertEncoded, cert.cbCertEncoded as usize) };
+ let id = Sha256::digest(cert_der).to_vec();
+ let id = id.to_vec();
+ let cert_info = unsafe { &*cert.pCertInfo };
+ let mut modulus = None;
+ let mut ec_params = None;
+ let spki = &cert_info.SubjectPublicKeyInfo;
+ let algorithm_oid = unsafe { CStr::from_ptr(spki.Algorithm.pszObjId) }
+ .to_str()
+ .map_err(|_| error_here!(ErrorType::ExternalError))?;
+ let (key_type_enum, key_type_attribute) = if algorithm_oid == szOID_RSA_RSA {
+ if spki.PublicKey.cUnusedBits != 0 {
+ return Err(error_here!(ErrorType::ExternalError));
+ }
+ let public_key_bytes = unsafe {
+ std::slice::from_raw_parts(spki.PublicKey.pbData, spki.PublicKey.cbData as usize)
+ };
+ let modulus_value = read_rsa_modulus(public_key_bytes)?;
+ modulus = Some(modulus_value);
+ (KeyType::RSA, CKK_RSA)
+ } else if algorithm_oid == szOID_ECC_PUBLIC_KEY {
+ let params = &spki.Algorithm.Parameters;
+ ec_params = Some(
+ unsafe { std::slice::from_raw_parts(params.pbData, params.cbData as usize) }
+ .to_vec(),
+ );
+ (KeyType::EC, CKK_EC)
+ } else {
+ return Err(error_here!(ErrorType::LibraryFailure));
+ };
+ let cert = CertContext::new(cert_context);
+ Ok(Key {
+ cert,
+ class: serialize_uint(CKO_PRIVATE_KEY)?,
+ token: serialize_uint(CK_TRUE)?,
+ id,
+ private: serialize_uint(CK_TRUE)?,
+ key_type: serialize_uint(key_type_attribute)?,
+ modulus,
+ ec_params,
+ key_type_enum,
+ slot_type: SlotType::Modern,
+ key_handle: None,
+ })
+ }
+
+ fn class(&self) -> &[u8] {
+ &self.class
+ }
+
+ fn token(&self) -> &[u8] {
+ &self.token
+ }
+
+ fn id(&self) -> &[u8] {
+ &self.id
+ }
+
+ fn private(&self) -> &[u8] {
+ &self.private
+ }
+
+ fn key_type(&self) -> &[u8] {
+ &self.key_type
+ }
+
+ fn modulus(&self) -> Option<&[u8]> {
+ match &self.modulus {
+ Some(modulus) => Some(modulus.as_slice()),
+ None => None,
+ }
+ }
+
+ fn ec_params(&self) -> Option<&[u8]> {
+ match &self.ec_params {
+ Some(ec_params) => Some(ec_params.as_slice()),
+ None => None,
+ }
+ }
+
+ fn sign_with_retry(
+ &mut self,
+ data: &[u8],
+ params: &Option<CK_RSA_PKCS_PSS_PARAMS>,
+ do_signature: bool,
+ ) -> Result<Vec<u8>, Error> {
+ let result = self.sign_internal(data, params, do_signature);
+ if result.is_ok() {
+ return result;
+ }
+ // Some devices appear to not work well when the key handle is held for too long or if a
+ // card is inserted/removed while Firefox is running. Try refreshing the key handle.
+ debug!("sign failed: refreshing key handle");
+ let _ = self.key_handle.take();
+ self.sign_internal(data, params, do_signature)
+ }
+
+ /// data: the data to sign
+ /// do_signature: if true, actually perform the signature. Otherwise, return a `Vec<u8>` of the
+ /// length the signature would be, if performed.
+ fn sign_internal(
+ &mut self,
+ data: &[u8],
+ params: &Option<CK_RSA_PKCS_PSS_PARAMS>,
+ do_signature: bool,
+ ) -> Result<Vec<u8>, Error> {
+ // If this key hasn't been used for signing yet, there won't be a cached key handle. Obtain
+ // and cache it if this is the case. Doing so can cause the underlying implementation to
+ // show an authentication or pin prompt to the user. Caching the handle can avoid causing
+ // multiple prompts to be displayed in some cases.
+ if self.key_handle.is_none() {
+ let _ = self.key_handle.replace(KeyHandle::from_cert(&self.cert)?);
+ }
+ let key = match &self.key_handle {
+ Some(key) => key,
+ None => return Err(error_here!(ErrorType::LibraryFailure)),
+ };
+ key.sign(data, params, do_signature, self.key_type_enum)
+ }
+}
+
+impl CryptokiObject for Key {
+ fn matches(&self, slot_type: SlotType, attrs: &[(CK_ATTRIBUTE_TYPE, Vec<u8>)]) -> bool {
+ if slot_type != self.slot_type {
+ return false;
+ }
+ for (attr_type, attr_value) in attrs {
+ let comparison = match *attr_type {
+ CKA_CLASS => self.class(),
+ CKA_TOKEN => self.token(),
+ CKA_ID => self.id(),
+ CKA_PRIVATE => self.private(),
+ CKA_KEY_TYPE => self.key_type(),
+ CKA_MODULUS => {
+ if let Some(modulus) = self.modulus() {
+ modulus
+ } else {
+ return false;
+ }
+ }
+ CKA_EC_PARAMS => {
+ if let Some(ec_params) = self.ec_params() {
+ ec_params
+ } else {
+ return false;
+ }
+ }
+ _ => return false,
+ };
+ if attr_value.as_slice() != comparison {
+ return false;
+ }
+ }
+ true
+ }
+
+ fn get_attribute(&self, attribute: CK_ATTRIBUTE_TYPE) -> Option<&[u8]> {
+ match attribute {
+ CKA_CLASS => Some(self.class()),
+ CKA_TOKEN => Some(self.token()),
+ CKA_ID => Some(self.id()),
+ CKA_PRIVATE => Some(self.private()),
+ CKA_KEY_TYPE => Some(self.key_type()),
+ CKA_MODULUS => self.modulus(),
+ CKA_EC_PARAMS => self.ec_params(),
+ _ => None,
+ }
+ }
+}
+
+impl Sign for Key {
+ fn get_signature_length(
+ &mut self,
+ data: &[u8],
+ params: &Option<CK_RSA_PKCS_PSS_PARAMS>,
+ ) -> Result<usize, Error> {
+ match self.sign_with_retry(data, params, false) {
+ Ok(dummy_signature_bytes) => Ok(dummy_signature_bytes.len()),
+ Err(e) => Err(e),
+ }
+ }
+
+ fn sign(
+ &mut self,
+ data: &[u8],
+ params: &Option<CK_RSA_PKCS_PSS_PARAMS>,
+ ) -> Result<Vec<u8>, Error> {
+ self.sign_with_retry(data, params, true)
+ }
+}
+
+struct CertStore {
+ handle: HCERTSTORE,
+}
+
+impl Drop for CertStore {
+ fn drop(&mut self) {
+ if !self.handle.is_null() {
+ unsafe {
+ CertCloseStore(self.handle, 0);
+ }
+ }
+ }
+}
+
+impl Deref for CertStore {
+ type Target = HCERTSTORE;
+
+ fn deref(&self) -> &Self::Target {
+ &self.handle
+ }
+}
+
+impl CertStore {
+ fn new(handle: HCERTSTORE) -> CertStore {
+ CertStore { handle }
+ }
+}
+
+// Given a pointer to a CERT_CHAIN_CONTEXT, enumerates each chain in the context and each element
+// in each chain to gather every CERT_CONTEXT pointed to by the CERT_CHAIN_CONTEXT.
+// https://docs.microsoft.com/en-us/windows/win32/api/wincrypt/ns-wincrypt-cert_chain_context says
+// that the 0th element of the 0th chain will be the end-entity certificate. This certificate (if
+// present), will be the 0th element of the returned Vec.
+fn gather_cert_contexts(cert_chain_context: *const CERT_CHAIN_CONTEXT) -> Vec<*const CERT_CONTEXT> {
+ let mut cert_contexts = Vec::new();
+ if cert_chain_context.is_null() {
+ return cert_contexts;
+ }
+ let cert_chain_context = unsafe { &*cert_chain_context };
+ let cert_chains = unsafe {
+ std::slice::from_raw_parts(
+ cert_chain_context.rgpChain,
+ cert_chain_context.cChain as usize,
+ )
+ };
+ for cert_chain in cert_chains {
+ // First dereference the borrow.
+ let cert_chain = *cert_chain;
+ if cert_chain.is_null() {
+ continue;
+ }
+ // Then dereference the pointer.
+ let cert_chain = unsafe { &*cert_chain };
+ let chain_elements = unsafe {
+ std::slice::from_raw_parts(cert_chain.rgpElement, cert_chain.cElement as usize)
+ };
+ for chain_element in chain_elements {
+ let chain_element = *chain_element; // dereference borrow
+ if chain_element.is_null() {
+ continue;
+ }
+ let chain_element = unsafe { &*chain_element }; // dereference pointer
+ cert_contexts.push(chain_element.pCertContext);
+ }
+ }
+ cert_contexts
+}
+
+pub struct Backend {}
+
+impl ClientCertsBackend for Backend {
+ type Cert = Cert;
+ type Key = Key;
+
+ /// Attempts to enumerate certificates with private keys exposed by the OS. Currently only looks in
+ /// the "My" cert store of the current user. In the future this may look in more locations.
+ fn find_objects(&self) -> Result<(Vec<Cert>, Vec<Key>), Error> {
+ let mut certs = Vec::new();
+ let mut keys = Vec::new();
+ let location_flags = CERT_SYSTEM_STORE_CURRENT_USER
+ | CERT_STORE_OPEN_EXISTING_FLAG
+ | CERT_STORE_READONLY_FLAG;
+ let store_name = match CString::new("My") {
+ Ok(store_name) => store_name,
+ Err(_) => return Err(error_here!(ErrorType::LibraryFailure)),
+ };
+ let store = CertStore::new(unsafe {
+ CertOpenStore(
+ CERT_STORE_PROV_SYSTEM_REGISTRY_A,
+ 0,
+ 0,
+ location_flags,
+ store_name.as_ptr() as *const winapi::ctypes::c_void,
+ )
+ });
+ if store.is_null() {
+ return Err(error_here!(ErrorType::ExternalError));
+ }
+ let find_params = CERT_CHAIN_FIND_ISSUER_PARA {
+ cbSize: std::mem::size_of::<CERT_CHAIN_FIND_ISSUER_PARA>() as u32,
+ pszUsageIdentifier: std::ptr::null(),
+ dwKeySpec: 0,
+ dwAcquirePrivateKeyFlags: 0,
+ cIssuer: 0,
+ rgIssuer: std::ptr::null_mut(),
+ pfnFindCallback: None,
+ pvFindArg: std::ptr::null_mut(),
+ pdwIssuerChainIndex: std::ptr::null_mut(),
+ pdwIssuerElementIndex: std::ptr::null_mut(),
+ };
+ let mut cert_chain_context: PCCERT_CHAIN_CONTEXT = std::ptr::null_mut();
+ loop {
+ // CertFindChainInStore finds all certificates with private keys in the store. It also
+ // attempts to build a verified certificate chain to a trust anchor for each certificate.
+ // We gather and hold onto these extra certificates so that gecko can use them when
+ // filtering potential client certificates according to the acceptable CAs list sent by
+ // servers when they request client certificates.
+ cert_chain_context = unsafe {
+ CertFindChainInStore(
+ *store,
+ X509_ASN_ENCODING,
+ CERT_CHAIN_FIND_BY_ISSUER_CACHE_ONLY_FLAG
+ | CERT_CHAIN_FIND_BY_ISSUER_CACHE_ONLY_URL_FLAG,
+ CERT_CHAIN_FIND_BY_ISSUER,
+ &find_params as *const CERT_CHAIN_FIND_ISSUER_PARA
+ as *const winapi::ctypes::c_void,
+ cert_chain_context,
+ )
+ };
+ if cert_chain_context.is_null() {
+ break;
+ }
+ let cert_contexts = gather_cert_contexts(cert_chain_context);
+ // The 0th CERT_CONTEXT is the end-entity (i.e. the certificate with the private key we're
+ // after).
+ match cert_contexts.get(0) {
+ Some(cert_context) => {
+ let key = match Key::new(*cert_context) {
+ Ok(key) => key,
+ Err(_) => continue,
+ };
+ let cert = match Cert::new(*cert_context) {
+ Ok(cert) => cert,
+ Err(_) => continue,
+ };
+ certs.push(cert);
+ keys.push(key);
+ }
+ None => {}
+ };
+ for cert_context in cert_contexts.iter().skip(1) {
+ if let Ok(cert) = Cert::new(*cert_context) {
+ certs.push(cert);
+ }
+ }
+ }
+ Ok((certs, keys))
+ }
+}
diff --git a/security/manager/ssl/osclientcerts/src/bindings_macos.rs b/security/manager/ssl/osclientcerts/src/bindings_macos.rs
new file mode 100644
index 0000000000..7294bbb555
--- /dev/null
+++ b/security/manager/ssl/osclientcerts/src/bindings_macos.rs
@@ -0,0 +1,79 @@
+/* -*- Mode: rust; rust-indent-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// This was originally generated by rust-bindgen at build time. Later in
+// development it became clear that using bindgen for this library as part of
+// mozilla-central would be difficult (if not impossible). So, this was
+// converted to a static file and unused declarations were removed. Also,
+// intermediate types added by rust-bindgen were removed for clarity.
+
+pub type OSStatus = i32;
+pub const errSecSuccess: OSStatus = 0;
+pub const errSecItemNotFound: OSStatus = -25300;
+
+pub type SecKeyAlgorithm = CFStringRef;
+
+extern "C" {
+ // Available starting macOS 10.3
+ pub fn SecCertificateGetTypeID() -> CFTypeID;
+ pub fn SecTrustCreateWithCertificates(
+ certificates: SecCertificateRef,
+ policies: SecPolicyRef,
+ trust: *mut SecTrustRef,
+ ) -> OSStatus;
+ pub fn SecIdentityGetTypeID() -> CFTypeID;
+ pub fn SecIdentityCopyCertificate(
+ identityRef: SecIdentityRef,
+ certificateRef: *mut SecCertificateRef,
+ ) -> OSStatus;
+ pub fn SecIdentityCopyPrivateKey(
+ identityRef: SecIdentityRef,
+ privateKeyRef: *mut SecKeyRef,
+ ) -> OSStatus;
+ pub fn SecKeyGetTypeID() -> CFTypeID;
+ pub fn SecPolicyGetTypeID() -> CFTypeID;
+ pub fn SecTrustGetTypeID() -> CFTypeID;
+
+ // Available starting macOS 10.6
+ pub fn SecCertificateCopyData(certificate: SecCertificateRef) -> CFDataRef;
+ pub fn SecCertificateCopySubjectSummary(certificate: SecCertificateRef) -> CFStringRef;
+ pub fn SecItemCopyMatching(query: CFDictionaryRef, result: *mut CFTypeRef) -> OSStatus;
+ pub fn SecPolicyCreateSSL(server: bool, hostname: CFStringRef) -> SecPolicyRef;
+ pub static kSecClass: CFStringRef;
+ pub static kSecAttrKeyType: CFStringRef;
+ pub static kSecAttrKeySizeInBits: CFStringRef;
+ pub static kSecMatchLimit: CFStringRef;
+ pub static kSecMatchLimitAll: CFStringRef;
+ pub static kSecReturnRef: CFStringRef;
+
+ // Available starting macOS 10.7
+ pub fn SecTrustGetCertificateAtIndex(trust: SecTrustRef, ix: CFIndex) -> SecCertificateRef;
+ pub fn SecTrustGetCertificateCount(trust: SecTrustRef) -> CFIndex;
+ pub static kSecClassIdentity: CFStringRef;
+ pub static kSecAttrKeyTypeRSA: CFStringRef;
+
+ // Available starting macOS 10.9
+ pub fn SecTrustSetNetworkFetchAllowed(trust: SecTrustRef, allowFetch: Boolean) -> OSStatus;
+
+ // Available starting macOS 10.12
+ pub fn SecKeyCreateSignature(
+ key: SecKeyRef,
+ algorithm: SecKeyAlgorithm,
+ data: CFDataRef,
+ err: *mut CFErrorRef,
+ ) -> CFDataRef;
+ pub fn SecKeyCopyAttributes(key: SecKeyRef) -> CFDictionaryRef;
+ pub fn SecKeyCopyExternalRepresentation(key: SecKeyRef, err: *mut CFErrorRef) -> CFDataRef;
+ pub static kSecKeyAlgorithmECDSASignatureDigestX962SHA1: CFStringRef;
+ pub static kSecKeyAlgorithmECDSASignatureDigestX962SHA256: CFStringRef;
+ pub static kSecKeyAlgorithmECDSASignatureDigestX962SHA384: CFStringRef;
+ pub static kSecKeyAlgorithmECDSASignatureDigestX962SHA512: CFStringRef;
+ pub static kSecKeyAlgorithmRSASignatureDigestPKCS1v15Raw: CFStringRef;
+ pub static kSecKeyAlgorithmRSASignatureDigestPKCS1v15SHA256: CFStringRef;
+ pub static kSecKeyAlgorithmRSASignatureDigestPKCS1v15SHA384: CFStringRef;
+ pub static kSecKeyAlgorithmRSASignatureDigestPKCS1v15SHA512: CFStringRef;
+ pub static kSecKeyAlgorithmRSASignatureDigestPKCS1v15SHA1: CFStringRef;
+ pub static kSecAttrKeyTypeECSECPrimeRandom: CFStringRef;
+}
diff --git a/security/manager/ssl/osclientcerts/src/lib.rs b/security/manager/ssl/osclientcerts/src/lib.rs
new file mode 100644
index 0000000000..a6cc206cfa
--- /dev/null
+++ b/security/manager/ssl/osclientcerts/src/lib.rs
@@ -0,0 +1,1235 @@
+/* -*- 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;
+#[cfg(target_os = "macos")]
+#[macro_use]
+extern crate lazy_static;
+#[cfg(target_os = "macos")]
+extern crate libloading;
+#[macro_use]
+extern crate log;
+extern crate pkcs11_bindings;
+#[macro_use]
+extern crate rsclientcerts;
+extern crate sha2;
+#[cfg(target_os = "windows")]
+extern crate winapi;
+
+use pkcs11_bindings::*;
+use rsclientcerts::manager::{ManagerProxy, SlotType};
+use std::ffi::CStr;
+use std::sync::Mutex;
+use std::thread;
+
+#[cfg(target_os = "macos")]
+mod backend_macos;
+#[cfg(target_os = "windows")]
+mod backend_windows;
+
+#[cfg(target_os = "macos")]
+use crate::backend_macos::Backend;
+#[cfg(target_os = "windows")]
+use crate::backend_windows::Backend;
+
+struct ModuleState {
+ manager_proxy: ManagerProxy,
+ mechanisms: Vec<CK_MECHANISM_TYPE>,
+}
+
+/// The singleton `ModuleState` 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` of the `ModuleState` forwards calls from any
+/// thread to a single thread where the real `Manager` does the actual work.
+static MODULE_STATE: Mutex<Option<ModuleState>> = 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 module_state_guard = try_to_get_module_state_guard!();
+// let manager = module_state_guard_to_manager!(module_state_guard);
+macro_rules! try_to_get_module_state_guard {
+ () => {
+ match MODULE_STATE.lock() {
+ Ok(maybe_module_state) => maybe_module_state,
+ Err(poison_error) => {
+ log_with_thread_id!(
+ error,
+ "previous thread panicked acquiring manager lock: {}",
+ poison_error
+ );
+ return CKR_DEVICE_ERROR;
+ }
+ }
+ };
+}
+
+macro_rules! module_state_guard_to_manager {
+ ($module_state_guard:ident) => {
+ match $module_state_guard.as_mut() {
+ Some(module_state) => &mut module_state.manager_proxy,
+ None => {
+ log_with_thread_id!(error, "module state expected to be set, but it is not");
+ return CKR_DEVICE_ERROR;
+ }
+ }
+ };
+}
+
+macro_rules! module_state_guard_to_mechanisms {
+ ($module_state_guard:ident) => {
+ match $module_state_guard.as_ref() {
+ Some(module_state) => &module_state.mechanisms,
+ None => {
+ log_with_thread_id!(error, "module state expected to be set, but it is not");
+ return CKR_DEVICE_ERROR;
+ }
+ }
+ };
+}
+
+// Helper macro to prefix log messages with the current thread ID.
+macro_rules! log_with_thread_id {
+ ($log_level:ident, $($message:expr),*) => {
+ $log_level!("{:?} {}", thread::current().id(), format_args!($($message),*));
+ };
+}
+
+/// This gets called to initialize the module. For this implementation, this consists of
+/// instantiating the `ManagerProxy`.
+extern "C" fn C_Initialize(pInitArgs: CK_VOID_PTR) -> CK_RV {
+ // This will fail if this has already been called, but this isn't a problem because either way,
+ // logging has been initialized.
+ let _ = env_logger::try_init();
+
+ if pInitArgs.is_null() {
+ return CKR_DEVICE_ERROR;
+ }
+ let init_args_ptr = unsafe { (*(pInitArgs as CK_C_INITIALIZE_ARGS_PTR)).pReserved };
+ if init_args_ptr.is_null() {
+ return CKR_DEVICE_ERROR;
+ }
+ let init_args_cstr = unsafe { CStr::from_ptr(init_args_ptr as *mut std::os::raw::c_char) };
+ let init_args = match init_args_cstr.to_str() {
+ Ok(init_args) => init_args,
+ Err(_) => return CKR_DEVICE_ERROR,
+ };
+ let mechanisms = if init_args == "RSA-PSS" {
+ vec![CKM_ECDSA, CKM_RSA_PKCS, CKM_RSA_PKCS_PSS]
+ } else {
+ vec![CKM_ECDSA, CKM_RSA_PKCS]
+ };
+ let mut module_state_guard = try_to_get_module_state_guard!();
+ let manager_proxy = match ManagerProxy::new(Backend {}) {
+ Ok(p) => p,
+ Err(e) => {
+ log_with_thread_id!(error, "C_Initialize: ManagerProxy: {}", e);
+ return CKR_DEVICE_ERROR;
+ }
+ };
+ match module_state_guard.replace(ModuleState {
+ manager_proxy,
+ mechanisms,
+ }) {
+ Some(_unexpected_previous_module_state) => {
+ #[cfg(target_os = "macos")]
+ {
+ log_with_thread_id!(info, "C_Initialize: module state previously set (this is expected on macOS - replacing it)");
+ }
+ #[cfg(target_os = "windows")]
+ {
+ log_with_thread_id!(
+ warn,
+ "C_Initialize: module state unexpectedly previously set (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 module_state_guard = try_to_get_module_state_guard!();
+ let manager = module_state_guard_to_manager!(module_state_guard);
+ match manager.stop() {
+ Ok(()) => {
+ log_with_thread_id!(debug, "C_Finalize: CKR_OK");
+ CKR_OK
+ }
+ Err(e) => {
+ log_with_thread_id!(error, "C_Finalize: CKR_DEVICE_ERROR: {}", e);
+ CKR_DEVICE_ERROR
+ }
+ }
+}
+
+// The specification mandates that these strings be padded with spaces to the appropriate length.
+// Since the length of fixed-size arrays in rust is part of the type, the compiler enforces that
+// these byte strings are of the correct length.
+const MANUFACTURER_ID_BYTES: &[u8; 32] = b"Mozilla Corporation ";
+const LIBRARY_DESCRIPTION_BYTES: &[u8; 32] = b"OS Client Cert Module ";
+
+/// This gets called to gather some information about the module. In particular, this implementation
+/// supports (portions of) cryptoki (PKCS #11) version 2.2.
+extern "C" fn C_GetInfo(pInfo: CK_INFO_PTR) -> CK_RV {
+ if pInfo.is_null() {
+ log_with_thread_id!(error, "C_GetInfo: CKR_ARGUMENTS_BAD");
+ return CKR_ARGUMENTS_BAD;
+ }
+ log_with_thread_id!(debug, "C_GetInfo: CKR_OK");
+ let mut info = CK_INFO::default();
+ info.cryptokiVersion.major = 2;
+ info.cryptokiVersion.minor = 2;
+ info.manufacturerID = *MANUFACTURER_ID_BYTES;
+ info.libraryDescription = *LIBRARY_DESCRIPTION_BYTES;
+ unsafe {
+ *pInfo = info;
+ }
+ CKR_OK
+}
+
+/// This module has one slot.
+const SLOT_COUNT: CK_ULONG = 1;
+const SLOT_ID: CK_SLOT_ID = 1;
+
+/// 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;
+ }
+ };
+ unsafe {
+ *pulCount = SLOT_COUNT;
+ }
+ log_with_thread_id!(debug, "C_GetSlotList: CKR_OK");
+ CKR_OK
+}
+
+const SLOT_DESCRIPTION_BYTES: &[u8; 64] =
+ b"OS Client Cert Slot ";
+
+/// This gets called to obtain information about slots. In this implementation, the token is
+/// always present in the singular slot.
+extern "C" fn C_GetSlotInfo(slotID: CK_SLOT_ID, pInfo: CK_SLOT_INFO_PTR) -> CK_RV {
+ if slotID != SLOT_ID || pInfo.is_null() {
+ log_with_thread_id!(error, "C_GetSlotInfo: CKR_ARGUMENTS_BAD");
+ return CKR_ARGUMENTS_BAD;
+ }
+ let slot_info = CK_SLOT_INFO {
+ slotDescription: *SLOT_DESCRIPTION_BYTES,
+ 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_BYTES: &[u8; 32] = b"OS Client Cert Token ";
+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 one slot,
+/// so it has one token. 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 || 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();
+ token_info.label = *TOKEN_LABEL_BYTES;
+ 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 singular slot supports
+/// ECDSA and RSA PKCS1. Depending on the configuration the module was loaded with, it may also
+/// support RSA PSS.
+extern "C" fn C_GetMechanismList(
+ slotID: CK_SLOT_ID,
+ pMechanismList: CK_MECHANISM_TYPE_PTR,
+ pulCount: CK_ULONG_PTR,
+) -> CK_RV {
+ if slotID != SLOT_ID || pulCount.is_null() {
+ log_with_thread_id!(error, "C_GetMechanismList: CKR_ARGUMENTS_BAD");
+ return CKR_ARGUMENTS_BAD;
+ }
+ let module_state_guard = try_to_get_module_state_guard!();
+ let mechanisms = module_state_guard_to_mechanisms!(module_state_guard);
+ if !pMechanismList.is_null() {
+ if unsafe { *pulCount as usize } < mechanisms.len() {
+ log_with_thread_id!(error, "C_GetMechanismList: CKR_ARGUMENTS_BAD");
+ return CKR_ARGUMENTS_BAD;
+ }
+ for (i, mechanism) in mechanisms.iter().enumerate() {
+ unsafe {
+ *pMechanismList.add(i) = *mechanism;
+ }
+ }
+ }
+ unsafe {
+ *pulCount = mechanisms.len() as CK_ULONG;
+ }
+ log_with_thread_id!(debug, "C_GetMechanismList: CKR_OK");
+ CKR_OK
+}
+
+extern "C" fn C_GetMechanismInfo(
+ _slotID: CK_SLOT_ID,
+ _type: CK_MECHANISM_TYPE,
+ _pInfo: CK_MECHANISM_INFO_PTR,
+) -> CK_RV {
+ log_with_thread_id!(error, "C_GetMechanismInfo: CKR_FUNCTION_NOT_SUPPORTED");
+ CKR_FUNCTION_NOT_SUPPORTED
+}
+
+extern "C" fn C_InitToken(
+ _slotID: CK_SLOT_ID,
+ _pPin: CK_UTF8CHAR_PTR,
+ _ulPinLen: CK_ULONG,
+ _pLabel: CK_UTF8CHAR_PTR,
+) -> CK_RV {
+ log_with_thread_id!(error, "C_InitToken: CKR_FUNCTION_NOT_SUPPORTED");
+ CKR_FUNCTION_NOT_SUPPORTED
+}
+
+extern "C" fn C_InitPIN(
+ _hSession: CK_SESSION_HANDLE,
+ _pPin: CK_UTF8CHAR_PTR,
+ _ulPinLen: CK_ULONG,
+) -> CK_RV {
+ log_with_thread_id!(error, "C_InitPIN: CKR_FUNCTION_NOT_SUPPORTED");
+ CKR_FUNCTION_NOT_SUPPORTED
+}
+
+extern "C" fn C_SetPIN(
+ _hSession: CK_SESSION_HANDLE,
+ _pOldPin: CK_UTF8CHAR_PTR,
+ _ulOldLen: CK_ULONG,
+ _pNewPin: CK_UTF8CHAR_PTR,
+ _ulNewLen: CK_ULONG,
+) -> CK_RV {
+ log_with_thread_id!(error, "C_SetPIN: CKR_FUNCTION_NOT_SUPPORTED");
+ CKR_FUNCTION_NOT_SUPPORTED
+}
+
+/// This gets called to create a new session. This module defers to the `ManagerProxy` to implement
+/// this.
+extern "C" fn C_OpenSession(
+ slotID: CK_SLOT_ID,
+ _flags: CK_FLAGS,
+ _pApplication: CK_VOID_PTR,
+ _Notify: CK_NOTIFY,
+ phSession: CK_SESSION_HANDLE_PTR,
+) -> CK_RV {
+ if slotID != SLOT_ID || phSession.is_null() {
+ log_with_thread_id!(error, "C_OpenSession: CKR_ARGUMENTS_BAD");
+ return CKR_ARGUMENTS_BAD;
+ }
+ let mut module_state_guard = try_to_get_module_state_guard!();
+ let manager = module_state_guard_to_manager!(module_state_guard);
+ // The "modern"/"legacy" slot distinction still exists in ipcclientcerts,
+ // which shares some library code with this module, to allow for a more
+ // nuanced notion of whether or not e.g. RSA-PSS is supported.
+ let session_handle = match manager.open_session(SlotType::Modern) {
+ Ok(session_handle) => session_handle,
+ Err(e) => {
+ log_with_thread_id!(error, "C_OpenSession: open_session failed: {}", e);
+ return CKR_DEVICE_ERROR;
+ }
+ };
+ unsafe {
+ *phSession = session_handle;
+ }
+ log_with_thread_id!(debug, "C_OpenSession: CKR_OK");
+ CKR_OK
+}
+
+/// This gets called to close a session. This is handled by the `ManagerProxy`.
+extern "C" fn C_CloseSession(hSession: CK_SESSION_HANDLE) -> CK_RV {
+ let mut module_state_guard = try_to_get_module_state_guard!();
+ let manager = module_state_guard_to_manager!(module_state_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 {
+ log_with_thread_id!(error, "C_CloseAllSessions: CKR_ARGUMENTS_BAD");
+ return CKR_ARGUMENTS_BAD;
+ }
+ let mut module_state_guard = try_to_get_module_state_guard!();
+ let manager = module_state_guard_to_manager!(module_state_guard);
+ match manager.close_all_sessions(SlotType::Modern) {
+ Ok(()) => {
+ log_with_thread_id!(debug, "C_CloseAllSessions: CKR_OK");
+ CKR_OK
+ }
+ Err(e) => {
+ log_with_thread_id!(
+ error,
+ "C_CloseAllSessions: close_all_sessions failed: {}",
+ e
+ );
+ CKR_DEVICE_ERROR
+ }
+ }
+}
+
+extern "C" fn C_GetSessionInfo(_hSession: CK_SESSION_HANDLE, _pInfo: CK_SESSION_INFO_PTR) -> CK_RV {
+ log_with_thread_id!(error, "C_GetSessionInfo: CKR_FUNCTION_NOT_SUPPORTED");
+ CKR_FUNCTION_NOT_SUPPORTED
+}
+
+extern "C" fn C_GetOperationState(
+ _hSession: CK_SESSION_HANDLE,
+ _pOperationState: CK_BYTE_PTR,
+ _pulOperationStateLen: CK_ULONG_PTR,
+) -> CK_RV {
+ log_with_thread_id!(error, "C_GetOperationState: CKR_FUNCTION_NOT_SUPPORTED");
+ CKR_FUNCTION_NOT_SUPPORTED
+}
+
+extern "C" fn C_SetOperationState(
+ _hSession: CK_SESSION_HANDLE,
+ _pOperationState: CK_BYTE_PTR,
+ _ulOperationStateLen: CK_ULONG,
+ _hEncryptionKey: CK_OBJECT_HANDLE,
+ _hAuthenticationKey: CK_OBJECT_HANDLE,
+) -> CK_RV {
+ log_with_thread_id!(error, "C_SetOperationState: CKR_FUNCTION_NOT_SUPPORTED");
+ CKR_FUNCTION_NOT_SUPPORTED
+}
+
+extern "C" fn C_Login(
+ _hSession: CK_SESSION_HANDLE,
+ _userType: CK_USER_TYPE,
+ _pPin: CK_UTF8CHAR_PTR,
+ _ulPinLen: CK_ULONG,
+) -> CK_RV {
+ log_with_thread_id!(error, "C_Login: CKR_FUNCTION_NOT_SUPPORTED");
+ CKR_FUNCTION_NOT_SUPPORTED
+}
+
+/// This gets called to log out and drop any authenticated resources. Because this module does not
+/// hold on to authenticated resources, this module "implements" this by doing nothing and
+/// returning a success result.
+extern "C" fn C_Logout(_hSession: CK_SESSION_HANDLE) -> CK_RV {
+ log_with_thread_id!(debug, "C_Logout: CKR_OK");
+ CKR_OK
+}
+
+extern "C" fn C_CreateObject(
+ _hSession: CK_SESSION_HANDLE,
+ _pTemplate: CK_ATTRIBUTE_PTR,
+ _ulCount: CK_ULONG,
+ _phObject: CK_OBJECT_HANDLE_PTR,
+) -> CK_RV {
+ log_with_thread_id!(error, "C_CreateObject: CKR_FUNCTION_NOT_SUPPORTED");
+ CKR_FUNCTION_NOT_SUPPORTED
+}
+
+extern "C" fn C_CopyObject(
+ _hSession: CK_SESSION_HANDLE,
+ _hObject: CK_OBJECT_HANDLE,
+ _pTemplate: CK_ATTRIBUTE_PTR,
+ _ulCount: CK_ULONG,
+ _phNewObject: CK_OBJECT_HANDLE_PTR,
+) -> CK_RV {
+ log_with_thread_id!(error, "C_CopyObject: CKR_FUNCTION_NOT_SUPPORTED");
+ CKR_FUNCTION_NOT_SUPPORTED
+}
+
+extern "C" fn C_DestroyObject(_hSession: CK_SESSION_HANDLE, _hObject: CK_OBJECT_HANDLE) -> CK_RV {
+ log_with_thread_id!(error, "C_DestroyObject: CKR_FUNCTION_NOT_SUPPORTED");
+ CKR_FUNCTION_NOT_SUPPORTED
+}
+
+extern "C" fn C_GetObjectSize(
+ _hSession: CK_SESSION_HANDLE,
+ _hObject: CK_OBJECT_HANDLE,
+ _pulSize: CK_ULONG_PTR,
+) -> CK_RV {
+ log_with_thread_id!(error, "C_GetObjectSize: CKR_FUNCTION_NOT_SUPPORTED");
+ CKR_FUNCTION_NOT_SUPPORTED
+}
+
+/// This gets called to obtain the values of a number of attributes of an object identified by the
+/// given handle. This module implements this by requesting that the `ManagerProxy` find the object
+/// and attempt to get the value of each attribute. If a specified attribute is not defined on the
+/// object, the length of that attribute is set to -1 to indicate that it is not available.
+/// This gets called twice: once to obtain the lengths of the attributes and again to get the
+/// values.
+extern "C" fn C_GetAttributeValue(
+ _hSession: CK_SESSION_HANDLE,
+ hObject: CK_OBJECT_HANDLE,
+ pTemplate: CK_ATTRIBUTE_PTR,
+ ulCount: CK_ULONG,
+) -> CK_RV {
+ if pTemplate.is_null() {
+ log_with_thread_id!(error, "C_GetAttributeValue: CKR_ARGUMENTS_BAD");
+ return CKR_ARGUMENTS_BAD;
+ }
+ let mut attr_types = Vec::with_capacity(ulCount as usize);
+ for i in 0..ulCount as usize {
+ let attr = unsafe { &*pTemplate.add(i) };
+ attr_types.push(attr.type_);
+ }
+ let mut module_state_guard = try_to_get_module_state_guard!();
+ let manager = module_state_guard_to_manager!(module_state_guard);
+ let values = match manager.get_attributes(hObject, attr_types) {
+ Ok(values) => values,
+ Err(e) => {
+ log_with_thread_id!(error, "C_GetAttributeValue: CKR_ARGUMENTS_BAD ({})", e);
+ return CKR_ARGUMENTS_BAD;
+ }
+ };
+ if values.len() != ulCount as usize {
+ log_with_thread_id!(
+ error,
+ "C_GetAttributeValue: manager.get_attributes didn't return the right number of values"
+ );
+ return CKR_DEVICE_ERROR;
+ }
+ for (i, value) in values.iter().enumerate().take(ulCount as usize) {
+ let mut attr = unsafe { &mut *pTemplate.add(i) };
+ if let Some(attr_value) = value {
+ if attr.pValue.is_null() {
+ attr.ulValueLen = attr_value.len() as CK_ULONG;
+ } else {
+ let ptr: *mut u8 = attr.pValue as *mut u8;
+ if attr_value.len() != attr.ulValueLen as usize {
+ log_with_thread_id!(error, "C_GetAttributeValue: incorrect attr size");
+ return CKR_ARGUMENTS_BAD;
+ }
+ unsafe {
+ std::ptr::copy_nonoverlapping(attr_value.as_ptr(), ptr, attr_value.len());
+ }
+ }
+ } else {
+ attr.ulValueLen = (0 - 1) as CK_ULONG;
+ }
+ }
+ log_with_thread_id!(debug, "C_GetAttributeValue: CKR_OK");
+ CKR_OK
+}
+
+extern "C" fn C_SetAttributeValue(
+ _hSession: CK_SESSION_HANDLE,
+ _hObject: CK_OBJECT_HANDLE,
+ _pTemplate: CK_ATTRIBUTE_PTR,
+ _ulCount: CK_ULONG,
+) -> CK_RV {
+ log_with_thread_id!(error, "C_SetAttributeValue: CKR_FUNCTION_NOT_SUPPORTED");
+ CKR_FUNCTION_NOT_SUPPORTED
+}
+
+fn trace_attr(prefix: &str, attr: &CK_ATTRIBUTE) {
+ let typ = match unsafe_packed_field_access!(attr.type_) {
+ CKA_CLASS => "CKA_CLASS".to_string(),
+ CKA_TOKEN => "CKA_TOKEN".to_string(),
+ CKA_LABEL => "CKA_LABEL".to_string(),
+ CKA_ID => "CKA_ID".to_string(),
+ CKA_VALUE => "CKA_VALUE".to_string(),
+ CKA_ISSUER => "CKA_ISSUER".to_string(),
+ CKA_SERIAL_NUMBER => "CKA_SERIAL_NUMBER".to_string(),
+ CKA_SUBJECT => "CKA_SUBJECT".to_string(),
+ CKA_PRIVATE => "CKA_PRIVATE".to_string(),
+ CKA_KEY_TYPE => "CKA_KEY_TYPE".to_string(),
+ CKA_MODULUS => "CKA_MODULUS".to_string(),
+ CKA_EC_PARAMS => "CKA_EC_PARAMS".to_string(),
+ _ => format!("0x{:x}", unsafe_packed_field_access!(attr.type_)),
+ };
+ let value =
+ unsafe { std::slice::from_raw_parts(attr.pValue as *const u8, attr.ulValueLen as usize) };
+ log_with_thread_id!(
+ trace,
+ "{}CK_ATTRIBUTE {{ type: {}, pValue: {:?}, ulValueLen: {} }}",
+ prefix,
+ typ,
+ value,
+ unsafe_packed_field_access!(attr.ulValueLen)
+ );
+}
+
+/// This gets called to initialize a search for objects matching a given list of attributes. This
+/// module implements this by gathering the attributes and passing them to the `ManagerProxy` to
+/// start the search.
+extern "C" fn C_FindObjectsInit(
+ hSession: CK_SESSION_HANDLE,
+ pTemplate: CK_ATTRIBUTE_PTR,
+ ulCount: CK_ULONG,
+) -> CK_RV {
+ if pTemplate.is_null() {
+ log_with_thread_id!(error, "C_FindObjectsInit: CKR_ARGUMENTS_BAD");
+ return CKR_ARGUMENTS_BAD;
+ }
+ let mut attrs = Vec::new();
+ log_with_thread_id!(trace, "C_FindObjectsInit:");
+ for i in 0..ulCount as usize {
+ let attr = unsafe { &*pTemplate.add(i) };
+ trace_attr(" ", attr);
+ let slice = unsafe {
+ std::slice::from_raw_parts(attr.pValue as *const u8, attr.ulValueLen as usize)
+ };
+ attrs.push((attr.type_, slice.to_owned()));
+ }
+ let mut module_state_guard = try_to_get_module_state_guard!();
+ let manager = module_state_guard_to_manager!(module_state_guard);
+ match manager.start_search(hSession, attrs) {
+ Ok(()) => {}
+ Err(e) => {
+ log_with_thread_id!(error, "C_FindObjectsInit: CKR_ARGUMENTS_BAD: {}", e);
+ return CKR_ARGUMENTS_BAD;
+ }
+ }
+ log_with_thread_id!(debug, "C_FindObjectsInit: CKR_OK");
+ CKR_OK
+}
+
+/// This gets called after `C_FindObjectsInit` to get the results of a search. This module
+/// implements this by looking up the search in the `ManagerProxy` and copying out the matching
+/// object handles.
+extern "C" fn C_FindObjects(
+ hSession: CK_SESSION_HANDLE,
+ phObject: CK_OBJECT_HANDLE_PTR,
+ ulMaxObjectCount: CK_ULONG,
+ pulObjectCount: CK_ULONG_PTR,
+) -> CK_RV {
+ if phObject.is_null() || pulObjectCount.is_null() || ulMaxObjectCount == 0 {
+ log_with_thread_id!(error, "C_FindObjects: CKR_ARGUMENTS_BAD");
+ return CKR_ARGUMENTS_BAD;
+ }
+ let mut module_state_guard = try_to_get_module_state_guard!();
+ let manager = module_state_guard_to_manager!(module_state_guard);
+ let handles = match manager.search(hSession, ulMaxObjectCount as usize) {
+ Ok(handles) => handles,
+ Err(e) => {
+ log_with_thread_id!(error, "C_FindObjects: CKR_ARGUMENTS_BAD: {}", e);
+ return CKR_ARGUMENTS_BAD;
+ }
+ };
+ log_with_thread_id!(debug, "C_FindObjects: found handles {:?}", handles);
+ if handles.len() > ulMaxObjectCount as usize {
+ log_with_thread_id!(error, "C_FindObjects: manager returned too many handles");
+ return CKR_DEVICE_ERROR;
+ }
+ unsafe {
+ *pulObjectCount = handles.len() as CK_ULONG;
+ }
+ for (index, handle) in handles.iter().enumerate() {
+ if index < ulMaxObjectCount as usize {
+ unsafe {
+ *(phObject.add(index)) = *handle;
+ }
+ }
+ }
+ log_with_thread_id!(debug, "C_FindObjects: CKR_OK");
+ CKR_OK
+}
+
+/// This gets called after `C_FindObjectsInit` and `C_FindObjects` to finish a search. The module
+/// tells the `ManagerProxy` to clear the search.
+extern "C" fn C_FindObjectsFinal(hSession: CK_SESSION_HANDLE) -> CK_RV {
+ let mut module_state_guard = try_to_get_module_state_guard!();
+ let manager = module_state_guard_to_manager!(module_state_guard);
+ // It would be an error if there were no search for this session, but we can be permissive here.
+ match manager.clear_search(hSession) {
+ Ok(()) => {
+ log_with_thread_id!(debug, "C_FindObjectsFinal: CKR_OK");
+ CKR_OK
+ }
+ Err(e) => {
+ log_with_thread_id!(error, "C_FindObjectsFinal: clear_search failed: {}", e);
+ CKR_DEVICE_ERROR
+ }
+ }
+}
+
+extern "C" fn C_EncryptInit(
+ _hSession: CK_SESSION_HANDLE,
+ _pMechanism: CK_MECHANISM_PTR,
+ _hKey: CK_OBJECT_HANDLE,
+) -> CK_RV {
+ log_with_thread_id!(error, "C_EncryptInit: CKR_FUNCTION_NOT_SUPPORTED");
+ CKR_FUNCTION_NOT_SUPPORTED
+}
+
+extern "C" fn C_Encrypt(
+ _hSession: CK_SESSION_HANDLE,
+ _pData: CK_BYTE_PTR,
+ _ulDataLen: CK_ULONG,
+ _pEncryptedData: CK_BYTE_PTR,
+ _pulEncryptedDataLen: CK_ULONG_PTR,
+) -> CK_RV {
+ log_with_thread_id!(error, "C_Encrypt: CKR_FUNCTION_NOT_SUPPORTED");
+ CKR_FUNCTION_NOT_SUPPORTED
+}
+
+extern "C" fn C_EncryptUpdate(
+ _hSession: CK_SESSION_HANDLE,
+ _pPart: CK_BYTE_PTR,
+ _ulPartLen: CK_ULONG,
+ _pEncryptedPart: CK_BYTE_PTR,
+ _pulEncryptedPartLen: CK_ULONG_PTR,
+) -> CK_RV {
+ log_with_thread_id!(error, "C_EncryptUpdate: CKR_FUNCTION_NOT_SUPPORTED");
+ CKR_FUNCTION_NOT_SUPPORTED
+}
+
+extern "C" fn C_EncryptFinal(
+ _hSession: CK_SESSION_HANDLE,
+ _pLastEncryptedPart: CK_BYTE_PTR,
+ _pulLastEncryptedPartLen: CK_ULONG_PTR,
+) -> CK_RV {
+ log_with_thread_id!(error, "C_EncryptFinal: CKR_FUNCTION_NOT_SUPPORTED");
+ CKR_FUNCTION_NOT_SUPPORTED
+}
+
+extern "C" fn C_DecryptInit(
+ _hSession: CK_SESSION_HANDLE,
+ _pMechanism: CK_MECHANISM_PTR,
+ _hKey: CK_OBJECT_HANDLE,
+) -> CK_RV {
+ log_with_thread_id!(error, "C_DecryptInit: CKR_FUNCTION_NOT_SUPPORTED");
+ CKR_FUNCTION_NOT_SUPPORTED
+}
+
+extern "C" fn C_Decrypt(
+ _hSession: CK_SESSION_HANDLE,
+ _pEncryptedData: CK_BYTE_PTR,
+ _ulEncryptedDataLen: CK_ULONG,
+ _pData: CK_BYTE_PTR,
+ _pulDataLen: CK_ULONG_PTR,
+) -> CK_RV {
+ log_with_thread_id!(error, "C_Decrypt: CKR_FUNCTION_NOT_SUPPORTED");
+ CKR_FUNCTION_NOT_SUPPORTED
+}
+
+extern "C" fn C_DecryptUpdate(
+ _hSession: CK_SESSION_HANDLE,
+ _pEncryptedPart: CK_BYTE_PTR,
+ _ulEncryptedPartLen: CK_ULONG,
+ _pPart: CK_BYTE_PTR,
+ _pulPartLen: CK_ULONG_PTR,
+) -> CK_RV {
+ log_with_thread_id!(error, "C_DecryptUpdate: CKR_FUNCTION_NOT_SUPPORTED");
+ CKR_FUNCTION_NOT_SUPPORTED
+}
+
+extern "C" fn C_DecryptFinal(
+ _hSession: CK_SESSION_HANDLE,
+ _pLastPart: CK_BYTE_PTR,
+ _pulLastPartLen: CK_ULONG_PTR,
+) -> CK_RV {
+ log_with_thread_id!(error, "C_DecryptFinal: CKR_FUNCTION_NOT_SUPPORTED");
+ CKR_FUNCTION_NOT_SUPPORTED
+}
+
+extern "C" fn C_DigestInit(_hSession: CK_SESSION_HANDLE, _pMechanism: CK_MECHANISM_PTR) -> CK_RV {
+ log_with_thread_id!(error, "C_DigestInit: CKR_FUNCTION_NOT_SUPPORTED");
+ CKR_FUNCTION_NOT_SUPPORTED
+}
+
+extern "C" fn C_Digest(
+ _hSession: CK_SESSION_HANDLE,
+ _pData: CK_BYTE_PTR,
+ _ulDataLen: CK_ULONG,
+ _pDigest: CK_BYTE_PTR,
+ _pulDigestLen: CK_ULONG_PTR,
+) -> CK_RV {
+ log_with_thread_id!(error, "C_Digest: CKR_FUNCTION_NOT_SUPPORTED");
+ CKR_FUNCTION_NOT_SUPPORTED
+}
+
+extern "C" fn C_DigestUpdate(
+ _hSession: CK_SESSION_HANDLE,
+ _pPart: CK_BYTE_PTR,
+ _ulPartLen: CK_ULONG,
+) -> CK_RV {
+ log_with_thread_id!(error, "C_DigestUpdate: CKR_FUNCTION_NOT_SUPPORTED");
+ CKR_FUNCTION_NOT_SUPPORTED
+}
+
+extern "C" fn C_DigestKey(_hSession: CK_SESSION_HANDLE, _hKey: CK_OBJECT_HANDLE) -> CK_RV {
+ log_with_thread_id!(error, "C_DigestKey: CKR_FUNCTION_NOT_SUPPORTED");
+ CKR_FUNCTION_NOT_SUPPORTED
+}
+
+extern "C" fn C_DigestFinal(
+ _hSession: CK_SESSION_HANDLE,
+ _pDigest: CK_BYTE_PTR,
+ _pulDigestLen: CK_ULONG_PTR,
+) -> CK_RV {
+ log_with_thread_id!(error, "C_DigestFinal: CKR_FUNCTION_NOT_SUPPORTED");
+ CKR_FUNCTION_NOT_SUPPORTED
+}
+
+/// This gets called to set up a sign operation. The module essentially defers to the
+/// `ManagerProxy`.
+extern "C" fn C_SignInit(
+ hSession: CK_SESSION_HANDLE,
+ pMechanism: CK_MECHANISM_PTR,
+ hKey: CK_OBJECT_HANDLE,
+) -> CK_RV {
+ if pMechanism.is_null() {
+ log_with_thread_id!(error, "C_SignInit: CKR_ARGUMENTS_BAD");
+ return CKR_ARGUMENTS_BAD;
+ }
+ // Presumably we should validate the mechanism against hKey, but the specification doesn't
+ // actually seem to require this.
+ let mechanism = unsafe { *pMechanism };
+ log_with_thread_id!(debug, "C_SignInit: mechanism is {:?}", mechanism);
+ let mechanism_params = if mechanism.mechanism == CKM_RSA_PKCS_PSS {
+ if mechanism.ulParameterLen as usize != std::mem::size_of::<CK_RSA_PKCS_PSS_PARAMS>() {
+ log_with_thread_id!(
+ error,
+ "C_SignInit: bad ulParameterLen for CKM_RSA_PKCS_PSS: {}",
+ unsafe_packed_field_access!(mechanism.ulParameterLen)
+ );
+ return CKR_ARGUMENTS_BAD;
+ }
+ Some(unsafe { *(mechanism.pParameter as *const CK_RSA_PKCS_PSS_PARAMS) })
+ } else {
+ None
+ };
+ let mut module_state_guard = try_to_get_module_state_guard!();
+ let manager = module_state_guard_to_manager!(module_state_guard);
+ match manager.start_sign(hSession, hKey, mechanism_params) {
+ Ok(()) => {}
+ Err(e) => {
+ log_with_thread_id!(error, "C_SignInit: CKR_GENERAL_ERROR: {}", e);
+ return CKR_GENERAL_ERROR;
+ }
+ };
+ log_with_thread_id!(debug, "C_SignInit: CKR_OK");
+ CKR_OK
+}
+
+/// NSS calls this after `C_SignInit` (there are more ways in the PKCS #11 specification to sign
+/// data, but this is the only way supported by this module). The module essentially defers to the
+/// `ManagerProxy` and copies out the resulting signature.
+extern "C" fn C_Sign(
+ hSession: CK_SESSION_HANDLE,
+ pData: CK_BYTE_PTR,
+ ulDataLen: CK_ULONG,
+ pSignature: CK_BYTE_PTR,
+ pulSignatureLen: CK_ULONG_PTR,
+) -> CK_RV {
+ if pData.is_null() || pulSignatureLen.is_null() {
+ log_with_thread_id!(error, "C_Sign: CKR_ARGUMENTS_BAD");
+ return CKR_ARGUMENTS_BAD;
+ }
+ let data = unsafe { std::slice::from_raw_parts(pData, ulDataLen as usize) };
+ if pSignature.is_null() {
+ let mut module_state_guard = try_to_get_module_state_guard!();
+ let manager = module_state_guard_to_manager!(module_state_guard);
+ match manager.get_signature_length(hSession, data.to_vec()) {
+ Ok(signature_length) => unsafe {
+ *pulSignatureLen = signature_length as CK_ULONG;
+ },
+ Err(e) => {
+ log_with_thread_id!(error, "C_Sign: get_signature_length failed: {}", e);
+ log_with_thread_id!(error, "C_Sign: try setting security.osclientcerts.assume_rsa_pss_support to false and restarting");
+ return CKR_GENERAL_ERROR;
+ }
+ }
+ } else {
+ let mut module_state_guard = try_to_get_module_state_guard!();
+ let manager = module_state_guard_to_manager!(module_state_guard);
+ match manager.sign(hSession, data.to_vec()) {
+ Ok(signature) => {
+ let signature_capacity = unsafe { *pulSignatureLen } as usize;
+ if signature_capacity < signature.len() {
+ log_with_thread_id!(error, "C_Sign: CKR_ARGUMENTS_BAD");
+ return CKR_ARGUMENTS_BAD;
+ }
+ let ptr: *mut u8 = pSignature as *mut u8;
+ unsafe {
+ std::ptr::copy_nonoverlapping(signature.as_ptr(), ptr, signature.len());
+ *pulSignatureLen = signature.len() as CK_ULONG;
+ }
+ }
+ Err(e) => {
+ log_with_thread_id!(error, "C_Sign: sign failed: {}", e);
+ log_with_thread_id!(error, "C_Sign: try setting security.osclientcerts.assume_rsa_pss_support to false and restarting");
+ return CKR_GENERAL_ERROR;
+ }
+ }
+ }
+ log_with_thread_id!(debug, "C_Sign: CKR_OK");
+ CKR_OK
+}
+
+extern "C" fn C_SignUpdate(
+ _hSession: CK_SESSION_HANDLE,
+ _pPart: CK_BYTE_PTR,
+ _ulPartLen: CK_ULONG,
+) -> CK_RV {
+ log_with_thread_id!(error, "C_SignUpdate: CKR_FUNCTION_NOT_SUPPORTED");
+ CKR_FUNCTION_NOT_SUPPORTED
+}
+
+extern "C" fn C_SignFinal(
+ _hSession: CK_SESSION_HANDLE,
+ _pSignature: CK_BYTE_PTR,
+ _pulSignatureLen: CK_ULONG_PTR,
+) -> CK_RV {
+ log_with_thread_id!(error, "C_SignFinal: CKR_FUNCTION_NOT_SUPPORTED");
+ CKR_FUNCTION_NOT_SUPPORTED
+}
+
+extern "C" fn C_SignRecoverInit(
+ _hSession: CK_SESSION_HANDLE,
+ _pMechanism: CK_MECHANISM_PTR,
+ _hKey: CK_OBJECT_HANDLE,
+) -> CK_RV {
+ log_with_thread_id!(error, "C_SignRecoverInit: CKR_FUNCTION_NOT_SUPPORTED");
+ CKR_FUNCTION_NOT_SUPPORTED
+}
+
+extern "C" fn C_SignRecover(
+ _hSession: CK_SESSION_HANDLE,
+ _pData: CK_BYTE_PTR,
+ _ulDataLen: CK_ULONG,
+ _pSignature: CK_BYTE_PTR,
+ _pulSignatureLen: CK_ULONG_PTR,
+) -> CK_RV {
+ log_with_thread_id!(error, "C_SignRecover: CKR_FUNCTION_NOT_SUPPORTED");
+ CKR_FUNCTION_NOT_SUPPORTED
+}
+
+extern "C" fn C_VerifyInit(
+ _hSession: CK_SESSION_HANDLE,
+ _pMechanism: CK_MECHANISM_PTR,
+ _hKey: CK_OBJECT_HANDLE,
+) -> CK_RV {
+ log_with_thread_id!(error, "C_VerifyInit: CKR_FUNCTION_NOT_SUPPORTED");
+ CKR_FUNCTION_NOT_SUPPORTED
+}
+
+extern "C" fn C_Verify(
+ _hSession: CK_SESSION_HANDLE,
+ _pData: CK_BYTE_PTR,
+ _ulDataLen: CK_ULONG,
+ _pSignature: CK_BYTE_PTR,
+ _ulSignatureLen: CK_ULONG,
+) -> CK_RV {
+ log_with_thread_id!(error, "C_Verify: CKR_FUNCTION_NOT_SUPPORTED");
+ CKR_FUNCTION_NOT_SUPPORTED
+}
+
+extern "C" fn C_VerifyUpdate(
+ _hSession: CK_SESSION_HANDLE,
+ _pPart: CK_BYTE_PTR,
+ _ulPartLen: CK_ULONG,
+) -> CK_RV {
+ log_with_thread_id!(error, "C_VerifyUpdate: CKR_FUNCTION_NOT_SUPPORTED");
+ CKR_FUNCTION_NOT_SUPPORTED
+}
+
+extern "C" fn C_VerifyFinal(
+ _hSession: CK_SESSION_HANDLE,
+ _pSignature: CK_BYTE_PTR,
+ _ulSignatureLen: CK_ULONG,
+) -> CK_RV {
+ log_with_thread_id!(error, "C_VerifyFinal: CKR_FUNCTION_NOT_SUPPORTED");
+ CKR_FUNCTION_NOT_SUPPORTED
+}
+
+extern "C" fn C_VerifyRecoverInit(
+ _hSession: CK_SESSION_HANDLE,
+ _pMechanism: CK_MECHANISM_PTR,
+ _hKey: CK_OBJECT_HANDLE,
+) -> CK_RV {
+ log_with_thread_id!(error, "C_VerifyRecoverInit: CKR_FUNCTION_NOT_SUPPORTED");
+ CKR_FUNCTION_NOT_SUPPORTED
+}
+
+extern "C" fn C_VerifyRecover(
+ _hSession: CK_SESSION_HANDLE,
+ _pSignature: CK_BYTE_PTR,
+ _ulSignatureLen: CK_ULONG,
+ _pData: CK_BYTE_PTR,
+ _pulDataLen: CK_ULONG_PTR,
+) -> CK_RV {
+ log_with_thread_id!(error, "C_VerifyRecover: CKR_FUNCTION_NOT_SUPPORTED");
+ CKR_FUNCTION_NOT_SUPPORTED
+}
+
+extern "C" fn C_DigestEncryptUpdate(
+ _hSession: CK_SESSION_HANDLE,
+ _pPart: CK_BYTE_PTR,
+ _ulPartLen: CK_ULONG,
+ _pEncryptedPart: CK_BYTE_PTR,
+ _pulEncryptedPartLen: CK_ULONG_PTR,
+) -> CK_RV {
+ log_with_thread_id!(error, "C_DigestEncryptUpdate: CKR_FUNCTION_NOT_SUPPORTED");
+ CKR_FUNCTION_NOT_SUPPORTED
+}
+
+extern "C" fn C_DecryptDigestUpdate(
+ _hSession: CK_SESSION_HANDLE,
+ _pEncryptedPart: CK_BYTE_PTR,
+ _ulEncryptedPartLen: CK_ULONG,
+ _pPart: CK_BYTE_PTR,
+ _pulPartLen: CK_ULONG_PTR,
+) -> CK_RV {
+ log_with_thread_id!(error, "C_DecryptDigestUpdate: CKR_FUNCTION_NOT_SUPPORTED");
+ CKR_FUNCTION_NOT_SUPPORTED
+}
+
+extern "C" fn C_SignEncryptUpdate(
+ _hSession: CK_SESSION_HANDLE,
+ _pPart: CK_BYTE_PTR,
+ _ulPartLen: CK_ULONG,
+ _pEncryptedPart: CK_BYTE_PTR,
+ _pulEncryptedPartLen: CK_ULONG_PTR,
+) -> CK_RV {
+ log_with_thread_id!(error, "C_SignEncryptUpdate: CKR_FUNCTION_NOT_SUPPORTED");
+ CKR_FUNCTION_NOT_SUPPORTED
+}
+
+extern "C" fn C_DecryptVerifyUpdate(
+ _hSession: CK_SESSION_HANDLE,
+ _pEncryptedPart: CK_BYTE_PTR,
+ _ulEncryptedPartLen: CK_ULONG,
+ _pPart: CK_BYTE_PTR,
+ _pulPartLen: CK_ULONG_PTR,
+) -> CK_RV {
+ log_with_thread_id!(error, "C_DecryptVerifyUpdate: CKR_FUNCTION_NOT_SUPPORTED");
+ CKR_FUNCTION_NOT_SUPPORTED
+}
+
+extern "C" fn C_GenerateKey(
+ _hSession: CK_SESSION_HANDLE,
+ _pMechanism: CK_MECHANISM_PTR,
+ _pTemplate: CK_ATTRIBUTE_PTR,
+ _ulCount: CK_ULONG,
+ _phKey: CK_OBJECT_HANDLE_PTR,
+) -> CK_RV {
+ log_with_thread_id!(error, "C_GenerateKey: CKR_FUNCTION_NOT_SUPPORTED");
+ CKR_FUNCTION_NOT_SUPPORTED
+}
+
+extern "C" fn C_GenerateKeyPair(
+ _hSession: CK_SESSION_HANDLE,
+ _pMechanism: CK_MECHANISM_PTR,
+ _pPublicKeyTemplate: CK_ATTRIBUTE_PTR,
+ _ulPublicKeyAttributeCount: CK_ULONG,
+ _pPrivateKeyTemplate: CK_ATTRIBUTE_PTR,
+ _ulPrivateKeyAttributeCount: CK_ULONG,
+ _phPublicKey: CK_OBJECT_HANDLE_PTR,
+ _phPrivateKey: CK_OBJECT_HANDLE_PTR,
+) -> CK_RV {
+ log_with_thread_id!(error, "C_GenerateKeyPair: CKR_FUNCTION_NOT_SUPPORTED");
+ CKR_FUNCTION_NOT_SUPPORTED
+}
+
+extern "C" fn C_WrapKey(
+ _hSession: CK_SESSION_HANDLE,
+ _pMechanism: CK_MECHANISM_PTR,
+ _hWrappingKey: CK_OBJECT_HANDLE,
+ _hKey: CK_OBJECT_HANDLE,
+ _pWrappedKey: CK_BYTE_PTR,
+ _pulWrappedKeyLen: CK_ULONG_PTR,
+) -> CK_RV {
+ log_with_thread_id!(error, "C_WrapKey: CKR_FUNCTION_NOT_SUPPORTED");
+ CKR_FUNCTION_NOT_SUPPORTED
+}
+
+extern "C" fn C_UnwrapKey(
+ _hSession: CK_SESSION_HANDLE,
+ _pMechanism: CK_MECHANISM_PTR,
+ _hUnwrappingKey: CK_OBJECT_HANDLE,
+ _pWrappedKey: CK_BYTE_PTR,
+ _ulWrappedKeyLen: CK_ULONG,
+ _pTemplate: CK_ATTRIBUTE_PTR,
+ _ulAttributeCount: CK_ULONG,
+ _phKey: CK_OBJECT_HANDLE_PTR,
+) -> CK_RV {
+ log_with_thread_id!(error, "C_UnwrapKey: CKR_FUNCTION_NOT_SUPPORTED");
+ CKR_FUNCTION_NOT_SUPPORTED
+}
+
+extern "C" fn C_DeriveKey(
+ _hSession: CK_SESSION_HANDLE,
+ _pMechanism: CK_MECHANISM_PTR,
+ _hBaseKey: CK_OBJECT_HANDLE,
+ _pTemplate: CK_ATTRIBUTE_PTR,
+ _ulAttributeCount: CK_ULONG,
+ _phKey: CK_OBJECT_HANDLE_PTR,
+) -> CK_RV {
+ log_with_thread_id!(error, "C_DeriveKey: CKR_FUNCTION_NOT_SUPPORTED");
+ CKR_FUNCTION_NOT_SUPPORTED
+}
+
+extern "C" fn C_SeedRandom(
+ _hSession: CK_SESSION_HANDLE,
+ _pSeed: CK_BYTE_PTR,
+ _ulSeedLen: CK_ULONG,
+) -> CK_RV {
+ log_with_thread_id!(error, "C_SeedRandom: CKR_FUNCTION_NOT_SUPPORTED");
+ CKR_FUNCTION_NOT_SUPPORTED
+}
+
+extern "C" fn C_GenerateRandom(
+ _hSession: CK_SESSION_HANDLE,
+ _RandomData: CK_BYTE_PTR,
+ _ulRandomLen: CK_ULONG,
+) -> CK_RV {
+ log_with_thread_id!(error, "C_GenerateRandom: CKR_FUNCTION_NOT_SUPPORTED");
+ CKR_FUNCTION_NOT_SUPPORTED
+}
+
+extern "C" fn C_GetFunctionStatus(_hSession: CK_SESSION_HANDLE) -> CK_RV {
+ log_with_thread_id!(error, "C_GetFunctionStatus: CKR_FUNCTION_NOT_SUPPORTED");
+ CKR_FUNCTION_NOT_SUPPORTED
+}
+
+extern "C" fn C_CancelFunction(_hSession: CK_SESSION_HANDLE) -> CK_RV {
+ log_with_thread_id!(error, "C_CancelFunction: CKR_FUNCTION_NOT_SUPPORTED");
+ CKR_FUNCTION_NOT_SUPPORTED
+}
+
+extern "C" fn C_WaitForSlotEvent(
+ _flags: CK_FLAGS,
+ _pSlot: CK_SLOT_ID_PTR,
+ _pRserved: CK_VOID_PTR,
+) -> CK_RV {
+ log_with_thread_id!(error, "C_WaitForSlotEvent: CKR_FUNCTION_NOT_SUPPORTED");
+ CKR_FUNCTION_NOT_SUPPORTED
+}
+
+/// To be a valid PKCS #11 module, this list of functions must be supported. At least cryptoki 2.2
+/// must be supported for this module to work in NSS.
+static mut FUNCTION_LIST: CK_FUNCTION_LIST = CK_FUNCTION_LIST {
+ version: CK_VERSION { major: 2, minor: 2 },
+ C_Initialize: Some(C_Initialize),
+ C_Finalize: Some(C_Finalize),
+ C_GetInfo: Some(C_GetInfo),
+ C_GetFunctionList: None,
+ C_GetSlotList: Some(C_GetSlotList),
+ C_GetSlotInfo: Some(C_GetSlotInfo),
+ C_GetTokenInfo: Some(C_GetTokenInfo),
+ C_GetMechanismList: Some(C_GetMechanismList),
+ C_GetMechanismInfo: Some(C_GetMechanismInfo),
+ C_InitToken: Some(C_InitToken),
+ C_InitPIN: Some(C_InitPIN),
+ C_SetPIN: Some(C_SetPIN),
+ C_OpenSession: Some(C_OpenSession),
+ C_CloseSession: Some(C_CloseSession),
+ C_CloseAllSessions: Some(C_CloseAllSessions),
+ C_GetSessionInfo: Some(C_GetSessionInfo),
+ C_GetOperationState: Some(C_GetOperationState),
+ C_SetOperationState: Some(C_SetOperationState),
+ C_Login: Some(C_Login),
+ C_Logout: Some(C_Logout),
+ C_CreateObject: Some(C_CreateObject),
+ C_CopyObject: Some(C_CopyObject),
+ C_DestroyObject: Some(C_DestroyObject),
+ C_GetObjectSize: Some(C_GetObjectSize),
+ C_GetAttributeValue: Some(C_GetAttributeValue),
+ C_SetAttributeValue: Some(C_SetAttributeValue),
+ C_FindObjectsInit: Some(C_FindObjectsInit),
+ C_FindObjects: Some(C_FindObjects),
+ C_FindObjectsFinal: Some(C_FindObjectsFinal),
+ C_EncryptInit: Some(C_EncryptInit),
+ C_Encrypt: Some(C_Encrypt),
+ C_EncryptUpdate: Some(C_EncryptUpdate),
+ C_EncryptFinal: Some(C_EncryptFinal),
+ C_DecryptInit: Some(C_DecryptInit),
+ C_Decrypt: Some(C_Decrypt),
+ C_DecryptUpdate: Some(C_DecryptUpdate),
+ C_DecryptFinal: Some(C_DecryptFinal),
+ C_DigestInit: Some(C_DigestInit),
+ C_Digest: Some(C_Digest),
+ C_DigestUpdate: Some(C_DigestUpdate),
+ C_DigestKey: Some(C_DigestKey),
+ C_DigestFinal: Some(C_DigestFinal),
+ C_SignInit: Some(C_SignInit),
+ C_Sign: Some(C_Sign),
+ C_SignUpdate: Some(C_SignUpdate),
+ C_SignFinal: Some(C_SignFinal),
+ C_SignRecoverInit: Some(C_SignRecoverInit),
+ C_SignRecover: Some(C_SignRecover),
+ C_VerifyInit: Some(C_VerifyInit),
+ C_Verify: Some(C_Verify),
+ C_VerifyUpdate: Some(C_VerifyUpdate),
+ C_VerifyFinal: Some(C_VerifyFinal),
+ C_VerifyRecoverInit: Some(C_VerifyRecoverInit),
+ C_VerifyRecover: Some(C_VerifyRecover),
+ C_DigestEncryptUpdate: Some(C_DigestEncryptUpdate),
+ C_DecryptDigestUpdate: Some(C_DecryptDigestUpdate),
+ C_SignEncryptUpdate: Some(C_SignEncryptUpdate),
+ C_DecryptVerifyUpdate: Some(C_DecryptVerifyUpdate),
+ C_GenerateKey: Some(C_GenerateKey),
+ C_GenerateKeyPair: Some(C_GenerateKeyPair),
+ C_WrapKey: Some(C_WrapKey),
+ C_UnwrapKey: Some(C_UnwrapKey),
+ C_DeriveKey: Some(C_DeriveKey),
+ C_SeedRandom: Some(C_SeedRandom),
+ C_GenerateRandom: Some(C_GenerateRandom),
+ C_GetFunctionStatus: Some(C_GetFunctionStatus),
+ C_CancelFunction: Some(C_CancelFunction),
+ C_WaitForSlotEvent: Some(C_WaitForSlotEvent),
+};
+
+/// # Safety
+///
+/// This is the only function this module exposes. NSS calls it to obtain the list of functions
+/// comprising this module.
+/// ppFunctionList must be a valid pointer.
+#[no_mangle]
+pub unsafe extern "C" fn C_GetFunctionList(ppFunctionList: CK_FUNCTION_LIST_PTR_PTR) -> CK_RV {
+ if ppFunctionList.is_null() {
+ return CKR_ARGUMENTS_BAD;
+ }
+ *ppFunctionList = &mut FUNCTION_LIST;
+ CKR_OK
+}
+
+#[cfg_attr(target_os = "macos", link(name = "Security", kind = "framework"))]
+extern "C" {}
diff --git a/security/manager/ssl/osclientcerts/test/certificate.bin b/security/manager/ssl/osclientcerts/test/certificate.bin
new file mode 100644
index 0000000000..01fbafb15c
--- /dev/null
+++ b/security/manager/ssl/osclientcerts/test/certificate.bin
Binary files differ
diff --git a/security/manager/ssl/osclientcerts/test/modulus.bin b/security/manager/ssl/osclientcerts/test/modulus.bin
new file mode 100644
index 0000000000..a9b09ff53e
--- /dev/null
+++ b/security/manager/ssl/osclientcerts/test/modulus.bin
@@ -0,0 +1,2 @@
+QDAn6=<5JhWl${%nkZukjdqzZE$~Ci8 ) I۹Lkl~i:*Ap f +$}ayW=;H7ӈC'UfZ~/:{$la[u1 ݴ% h{/"oi㴊a&%N6$,/1I#rQ1
+5 \ No newline at end of file
diff --git a/security/manager/ssl/osclientcerts/test/rsa.bin b/security/manager/ssl/osclientcerts/test/rsa.bin
new file mode 100644
index 0000000000..93ededb827
--- /dev/null
+++ b/security/manager/ssl/osclientcerts/test/rsa.bin
Binary files differ