From 2aa4a82499d4becd2284cdb482213d541b8804dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 28 Apr 2024 16:29:10 +0200 Subject: Adding upstream version 86.0.1. Signed-off-by: Daniel Baumann --- .../manager/ssl/osclientcerts/src/backend_macos.rs | 967 ++++++++++++++++ .../ssl/osclientcerts/src/backend_windows.rs | 933 +++++++++++++++ .../ssl/osclientcerts/src/bindings_macos.rs | 58 + security/manager/ssl/osclientcerts/src/lib.rs | 1208 ++++++++++++++++++++ security/manager/ssl/osclientcerts/src/manager.rs | 614 ++++++++++ security/manager/ssl/osclientcerts/src/util.rs | 463 ++++++++ 6 files changed, 4243 insertions(+) create mode 100644 security/manager/ssl/osclientcerts/src/backend_macos.rs create mode 100644 security/manager/ssl/osclientcerts/src/backend_windows.rs create mode 100644 security/manager/ssl/osclientcerts/src/bindings_macos.rs create mode 100644 security/manager/ssl/osclientcerts/src/lib.rs create mode 100644 security/manager/ssl/osclientcerts/src/manager.rs create mode 100644 security/manager/ssl/osclientcerts/src/util.rs (limited to 'security/manager/ssl/osclientcerts/src') diff --git a/security/manager/ssl/osclientcerts/src/backend_macos.rs b/security/manager/ssl/osclientcerts/src/backend_macos.rs new file mode 100644 index 0000000000..9d1f1a6b72 --- /dev/null +++ b/security/manager/ssl/osclientcerts/src/backend_macos.rs @@ -0,0 +1,967 @@ +/* -*- Mode: rust; rust-indent-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#![allow(non_upper_case_globals)] + +use libloading::{Library, Symbol}; +use pkcs11::types::*; +use sha2::{Digest, Sha256}; +use std::collections::BTreeMap; +use std::convert::TryInto; +use std::os::raw::c_void; + +use core_foundation::array::*; +use core_foundation::base::*; +use core_foundation::boolean::*; +use core_foundation::data::*; +use core_foundation::dictionary::*; +use core_foundation::error::*; +use core_foundation::number::*; +use core_foundation::string::*; + +// Normally we would generate this with a build script, but macos is +// cross-compiled on linux, and we'd have to figure out e.g. include paths, +// etc.. This is easier. +include!("bindings_macos.rs"); + +use crate::manager::SlotType; +use crate::util::*; + +#[repr(C)] +pub struct __SecIdentity(c_void); +pub type SecIdentityRef = *const __SecIdentity; +declare_TCFType!(SecIdentity, SecIdentityRef); +impl_TCFType!(SecIdentity, SecIdentityRef, SecIdentityGetTypeID); + +#[repr(C)] +pub struct __SecCertificate(c_void); +pub type SecCertificateRef = *const __SecCertificate; +declare_TCFType!(SecCertificate, SecCertificateRef); +impl_TCFType!(SecCertificate, SecCertificateRef, SecCertificateGetTypeID); + +#[repr(C)] +pub struct __SecKey(c_void); +pub type SecKeyRef = *const __SecKey; +declare_TCFType!(SecKey, SecKeyRef); +impl_TCFType!(SecKey, SecKeyRef, SecKeyGetTypeID); + +#[repr(C)] +pub struct __SecPolicy(c_void); +pub type SecPolicyRef = *const __SecPolicy; +declare_TCFType!(SecPolicy, SecPolicyRef); +impl_TCFType!(SecPolicy, SecPolicyRef, SecPolicyGetTypeID); + +#[repr(C)] +pub struct __SecTrust(c_void); +pub type SecTrustRef = *const __SecTrust; +declare_TCFType!(SecTrust, SecTrustRef); +impl_TCFType!(SecTrust, SecTrustRef, SecTrustGetTypeID); + +type SecKeyCreateSignatureType = + unsafe extern "C" fn(SecKeyRef, SecKeyAlgorithm, CFDataRef, *mut CFErrorRef) -> CFDataRef; +type SecKeyCopyAttributesType = unsafe extern "C" fn(SecKeyRef) -> CFDictionaryRef; +type SecKeyCopyExternalRepresentationType = + unsafe extern "C" fn(SecKeyRef, *mut CFErrorRef) -> CFDataRef; +type SecCertificateCopyNormalizedIssuerSequenceType = + unsafe extern "C" fn(SecCertificateRef) -> CFDataRef; +type SecCertificateCopyNormalizedSubjectSequenceType = + unsafe extern "C" fn(SecCertificateRef) -> CFDataRef; +type SecCertificateCopyKeyType = unsafe extern "C" fn(SecCertificateRef) -> SecKeyRef; +type SecTrustEvaluateWithErrorType = + unsafe extern "C" fn(trust: SecTrustRef, error: *mut CFErrorRef) -> bool; + +#[derive(Ord, Eq, PartialOrd, PartialEq)] +enum SecStringConstant { + // These are available in macOS 10.12 + SecKeyAlgorithmECDSASignatureDigestX962SHA1, + SecKeyAlgorithmECDSASignatureDigestX962SHA256, + SecKeyAlgorithmECDSASignatureDigestX962SHA384, + SecKeyAlgorithmECDSASignatureDigestX962SHA512, + SecKeyAlgorithmRSASignatureDigestPKCS1v15Raw, + SecAttrKeyTypeECSECPrimeRandom, + // These are available in macOS 10.13 + SecKeyAlgorithmRSASignatureDigestPSSSHA1, + SecKeyAlgorithmRSASignatureDigestPSSSHA256, + SecKeyAlgorithmRSASignatureDigestPSSSHA384, + SecKeyAlgorithmRSASignatureDigestPSSSHA512, +} + +// NB: This is not meant to be used outside of this module. It has to be made public because +// `RentedSecurityFramework` must be public in the rental declaration. +pub struct SecurityFrameworkFunctions<'a> { + sec_key_create_signature: Symbol<'a, SecKeyCreateSignatureType>, + sec_key_copy_attributes: Symbol<'a, SecKeyCopyAttributesType>, + sec_key_copy_external_representation: Symbol<'a, SecKeyCopyExternalRepresentationType>, + sec_certificate_copy_normalized_issuer_sequence: + Symbol<'a, SecCertificateCopyNormalizedIssuerSequenceType>, + sec_certificate_copy_normalized_subject_sequence: + Symbol<'a, SecCertificateCopyNormalizedSubjectSequenceType>, + sec_certificate_copy_key: Symbol<'a, SecCertificateCopyKeyType>, + sec_trust_evaluate_with_error: Symbol<'a, SecTrustEvaluateWithErrorType>, + sec_string_constants: BTreeMap, +} + +rental! { + mod rent_libloading { + use super::*; + + #[rental] + pub struct RentedSecurityFramework { + library: Box, // Library needs to be StableDeref, hence the Box + functions: SecurityFrameworkFunctions<'library>, + } + } +} + +/// This implementation uses security framework functions and constants that +/// are not provided by the version of the SDK we build with. To work around +/// this, we attempt to open and dynamically load these functions and symbols +/// at runtime. Unfortunately this does mean that if a user is not on a new +/// enough version of macOS, they will not be able to use client certificates +/// from their keychain in Firefox until they upgrade. +struct SecurityFramework { + rental: Option, +} + +impl SecurityFramework { + fn new() -> SecurityFramework { + let library = match Library::new("/System/Library/Frameworks/Security.framework/Security") { + Ok(library) => library, + Err(_) => return SecurityFramework { rental: None }, + }; + match rent_libloading::RentedSecurityFramework::try_new::<_, ()>( + Box::new(library), + |library| unsafe { + let sec_key_create_signature = library + .get::(b"SecKeyCreateSignature\0") + .map_err(|_| ())?; + let sec_key_copy_attributes = library + .get::(b"SecKeyCopyAttributes\0") + .map_err(|_| ())?; + let sec_key_copy_external_representation = library + .get::( + b"SecKeyCopyExternalRepresentation\0", + ) + .map_err(|_| ())?; + let sec_certificate_copy_normalized_issuer_sequence = library + .get::( + b"SecCertificateCopyNormalizedIssuerSequence\0", + ) + .map_err(|_| ())?; + let sec_certificate_copy_normalized_subject_sequence = library + .get::( + b"SecCertificateCopyNormalizedSubjectSequence\0", + ) + .map_err(|_| ())?; + let sec_certificate_copy_key = library + .get::(b"SecCertificateCopyKey\0") + .map_err(|_| ())?; + let sec_trust_evaluate_with_error = library + .get::(b"SecTrustEvaluateWithError\0") + .map_err(|_| ())?; + let mut sec_string_constants = BTreeMap::new(); + let strings_to_load = vec![ + ( + b"kSecKeyAlgorithmECDSASignatureDigestX962SHA1\0".as_ref(), + SecStringConstant::SecKeyAlgorithmECDSASignatureDigestX962SHA1, + ), + ( + b"kSecKeyAlgorithmECDSASignatureDigestX962SHA256\0".as_ref(), + SecStringConstant::SecKeyAlgorithmECDSASignatureDigestX962SHA256, + ), + ( + b"kSecKeyAlgorithmECDSASignatureDigestX962SHA384\0".as_ref(), + SecStringConstant::SecKeyAlgorithmECDSASignatureDigestX962SHA384, + ), + ( + b"kSecKeyAlgorithmECDSASignatureDigestX962SHA512\0".as_ref(), + SecStringConstant::SecKeyAlgorithmECDSASignatureDigestX962SHA512, + ), + ( + b"kSecKeyAlgorithmRSASignatureDigestPKCS1v15Raw\0".as_ref(), + SecStringConstant::SecKeyAlgorithmRSASignatureDigestPKCS1v15Raw, + ), + ( + b"kSecKeyAlgorithmRSASignatureDigestPSSSHA1\0".as_ref(), + SecStringConstant::SecKeyAlgorithmRSASignatureDigestPSSSHA1, + ), + ( + b"kSecKeyAlgorithmRSASignatureDigestPSSSHA256\0".as_ref(), + SecStringConstant::SecKeyAlgorithmRSASignatureDigestPSSSHA256, + ), + ( + b"kSecKeyAlgorithmRSASignatureDigestPSSSHA384\0".as_ref(), + SecStringConstant::SecKeyAlgorithmRSASignatureDigestPSSSHA384, + ), + ( + b"kSecKeyAlgorithmRSASignatureDigestPSSSHA512\0".as_ref(), + SecStringConstant::SecKeyAlgorithmRSASignatureDigestPSSSHA512, + ), + ( + b"kSecAttrKeyTypeECSECPrimeRandom\0".as_ref(), + SecStringConstant::SecAttrKeyTypeECSECPrimeRandom, + ), + ]; + for (symbol_name, sec_string_constant) in strings_to_load { + let cfstring_symbol = library + .get::<*const CFStringRef>(symbol_name) + .map_err(|_| ())?; + let cfstring = CFString::wrap_under_create_rule(**cfstring_symbol); + sec_string_constants.insert(sec_string_constant, cfstring.to_string()); + } + Ok(SecurityFrameworkFunctions { + sec_key_create_signature, + sec_key_copy_attributes, + sec_key_copy_external_representation, + sec_certificate_copy_normalized_issuer_sequence, + sec_certificate_copy_normalized_subject_sequence, + sec_certificate_copy_key, + sec_trust_evaluate_with_error, + sec_string_constants, + }) + }, + ) { + Ok(rental) => SecurityFramework { + rental: Some(rental), + }, + Err(_) => SecurityFramework { rental: None }, + } + } + + /// SecKeyCreateSignature is available in macOS 10.12 + fn sec_key_create_signature( + &self, + key: &SecKey, + algorithm: SecKeyAlgorithm, + data_to_sign: &CFData, + ) -> Result { + match &self.rental { + Some(rental) => rental.rent(|framework| unsafe { + let mut error = std::ptr::null_mut(); + let result = (framework.sec_key_create_signature)( + key.as_concrete_TypeRef(), + algorithm, + data_to_sign.as_concrete_TypeRef(), + &mut error, + ); + if result.is_null() { + let error = CFError::wrap_under_create_rule(error); + error!("SecKeyCreateSignature failed: {}", error); + return Err(()); + } + Ok(CFData::wrap_under_create_rule(result)) + }), + None => Err(()), + } + } + + /// SecKeyCopyAttributes is available in macOS 10.12 + fn sec_key_copy_attributes(&self, key: &SecKey) -> Result, ()> { + match &self.rental { + Some(rental) => rental.rent(|framework| unsafe { + let result = (framework.sec_key_copy_attributes)(key.as_concrete_TypeRef()); + if result.is_null() { + error!("SecKeyCopyAttributes failed"); + return Err(()); + } + Ok(CFDictionary::wrap_under_create_rule(result)) + }), + None => Err(()), + } + } + + /// SecKeyCopyExternalRepresentation is available in macOS 10.12 + fn sec_key_copy_external_representation(&self, key: &SecKey) -> Result { + match &self.rental { + Some(rental) => rental.rent(|framework| unsafe { + let mut error = std::ptr::null_mut(); + let result = (framework.sec_key_copy_external_representation)( + key.as_concrete_TypeRef(), + &mut error, + ); + if result.is_null() { + let error = CFError::wrap_under_create_rule(error); + error!("SecKeyCopyExternalRepresentation failed: {}", error); + return Err(()); + } + Ok(CFData::wrap_under_create_rule(result)) + }), + None => Err(()), + } + } + + /// SecCertificateCopyNormalizedIssuerSequence is available in macOS 10.12.4 + fn sec_certificate_copy_normalized_issuer_sequence( + &self, + certificate: &SecCertificate, + ) -> Result { + match &self.rental { + Some(rental) => rental.rent(|framework| unsafe { + let result = (framework.sec_certificate_copy_normalized_issuer_sequence)( + certificate.as_concrete_TypeRef(), + ); + if result.is_null() { + error!("SecCertificateCopyNormalizedIssuerSequence failed"); + return Err(()); + } + Ok(CFData::wrap_under_create_rule(result)) + }), + None => Err(()), + } + } + + /// SecCertificateCopyNormalizedSubjectSequence is available in macOS 10.12.4 + fn sec_certificate_copy_normalized_subject_sequence( + &self, + certificate: &SecCertificate, + ) -> Result { + match &self.rental { + Some(rental) => rental.rent(|framework| unsafe { + let result = (framework.sec_certificate_copy_normalized_subject_sequence)( + certificate.as_concrete_TypeRef(), + ); + if result.is_null() { + error!("SecCertificateCopyNormalizedSubjectSequence failed"); + return Err(()); + } + Ok(CFData::wrap_under_create_rule(result)) + }), + None => Err(()), + } + } + + /// SecCertificateCopyKey is available in macOS 10.14 + fn sec_certificate_copy_key(&self, certificate: &SecCertificate) -> Result { + match &self.rental { + Some(rental) => rental.rent(|framework| unsafe { + let result = + (framework.sec_certificate_copy_key)(certificate.as_concrete_TypeRef()); + if result.is_null() { + error!("SecCertificateCopyKey failed"); + return Err(()); + } + Ok(SecKey::wrap_under_create_rule(result)) + }), + None => Err(()), + } + } + + /// SecTrustEvaluateWithError is available in macOS 10.14 + fn sec_trust_evaluate_with_error(&self, trust: &SecTrust) -> Result { + match &self.rental { + Some(rental) => rental.rent(|framework| unsafe { + Ok((framework.sec_trust_evaluate_with_error)( + trust.as_concrete_TypeRef(), + std::ptr::null_mut(), + )) + }), + None => Err(()), + } + } + + fn get_sec_string_constant( + &self, + sec_string_constant: SecStringConstant, + ) -> Result { + match &self.rental { + Some(rental) => rental.rent(|framework| { + match framework.sec_string_constants.get(&sec_string_constant) { + Some(string) => Ok(CFString::new(string)), + None => Err(()), + } + }), + None => Err(()), + } + } +} + +lazy_static! { + static ref SECURITY_FRAMEWORK: SecurityFramework = SecurityFramework::new(); +} + +fn sec_identity_copy_certificate(identity: &SecIdentity) -> Result { + let mut certificate = std::ptr::null(); + let status = + unsafe { SecIdentityCopyCertificate(identity.as_concrete_TypeRef(), &mut certificate) }; + if status != errSecSuccess { + error!("SecIdentityCopyCertificate failed: {}", status); + return Err(()); + } + if certificate.is_null() { + error!("couldn't get certificate from identity?"); + return Err(()); + } + Ok(unsafe { SecCertificate::wrap_under_create_rule(certificate) }) +} + +fn sec_certificate_copy_subject_summary(certificate: &SecCertificate) -> Result { + let result = unsafe { SecCertificateCopySubjectSummary(certificate.as_concrete_TypeRef()) }; + if result.is_null() { + error!("SecCertificateCopySubjectSummary failed"); + return Err(()); + } + Ok(unsafe { CFString::wrap_under_create_rule(result) }) +} + +fn sec_certificate_copy_data(certificate: &SecCertificate) -> Result { + let result = unsafe { SecCertificateCopyData(certificate.as_concrete_TypeRef()) }; + if result.is_null() { + error!("SecCertificateCopyData failed"); + return Err(()); + } + Ok(unsafe { CFData::wrap_under_create_rule(result) }) +} + +fn sec_identity_copy_private_key(identity: &SecIdentity) -> Result { + let mut key = std::ptr::null(); + let status = unsafe { SecIdentityCopyPrivateKey(identity.as_concrete_TypeRef(), &mut key) }; + if status != errSecSuccess { + error!("SecIdentityCopyPrivateKey failed: {}", status); + return Err(()); + } + if key.is_null() { + error!("SecIdentityCopyPrivateKey didn't set key?"); + return Err(()); + } + Ok(unsafe { SecKey::wrap_under_create_rule(key) }) +} + +pub struct Cert { + class: Vec, + token: Vec, + id: Vec, + label: Vec, + value: Vec, + issuer: Vec, + serial_number: Vec, + subject: Vec, +} + +impl Cert { + fn new_from_identity(identity: &SecIdentity) -> Result { + let certificate = sec_identity_copy_certificate(identity)?; + Cert::new_from_certificate(&certificate) + } + + fn new_from_certificate(certificate: &SecCertificate) -> Result { + let label = sec_certificate_copy_subject_summary(certificate)?; + let der = sec_certificate_copy_data(certificate)?; + let der = der.bytes().to_vec(); + let id = Sha256::digest(&der).to_vec(); + let issuer = + SECURITY_FRAMEWORK.sec_certificate_copy_normalized_issuer_sequence(certificate)?; + let serial_number = read_encoded_serial_number(&der)?; + let subject = + SECURITY_FRAMEWORK.sec_certificate_copy_normalized_subject_sequence(certificate)?; + Ok(Cert { + class: serialize_uint(CKO_CERTIFICATE)?, + token: serialize_uint(CK_TRUE)?, + id, + label: label.to_string().into_bytes(), + value: der, + issuer: issuer.bytes().to_vec(), + serial_number, + subject: subject.bytes().to_vec(), + }) + } + + fn class(&self) -> &[u8] { + &self.class + } + + fn token(&self) -> &[u8] { + &self.token + } + + pub fn id(&self) -> &[u8] { + &self.id + } + + fn label(&self) -> &[u8] { + &self.label + } + + fn value(&self) -> &[u8] { + &self.value + } + + fn issuer(&self) -> &[u8] { + &self.issuer + } + + fn serial_number(&self) -> &[u8] { + &self.serial_number + } + + fn subject(&self) -> &[u8] { + &self.subject + } + + fn matches(&self, attrs: &[(CK_ATTRIBUTE_TYPE, Vec)]) -> bool { + for (attr_type, attr_value) in attrs { + let comparison = match *attr_type { + CKA_CLASS => self.class(), + CKA_TOKEN => self.token(), + CKA_LABEL => self.label(), + CKA_ID => self.id(), + CKA_VALUE => self.value(), + CKA_ISSUER => self.issuer(), + CKA_SERIAL_NUMBER => self.serial_number(), + CKA_SUBJECT => self.subject(), + _ => return false, + }; + if attr_value.as_slice() != comparison { + return false; + } + } + true + } + + fn get_attribute(&self, attribute: CK_ATTRIBUTE_TYPE) -> Option<&[u8]> { + let result = match attribute { + CKA_CLASS => self.class(), + CKA_TOKEN => self.token(), + CKA_LABEL => self.label(), + CKA_ID => self.id(), + CKA_VALUE => self.value(), + CKA_ISSUER => self.issuer(), + CKA_SERIAL_NUMBER => self.serial_number(), + CKA_SUBJECT => self.subject(), + _ => return None, + }; + Some(result) + } +} + +#[derive(Clone, Copy, Debug)] +pub enum KeyType { + EC(usize), + RSA, +} + +enum SignParams { + EC(CFString), + RSA(CFString), +} + +impl SignParams { + fn new( + key_type: KeyType, + data_len: usize, + params: &Option, + ) -> Result { + match key_type { + KeyType::EC(_) => SignParams::new_ec_params(data_len), + KeyType::RSA => SignParams::new_rsa_params(params), + } + } + + fn new_ec_params(data_len: usize) -> Result { + let algorithm_id = match data_len { + 20 => SecStringConstant::SecKeyAlgorithmECDSASignatureDigestX962SHA1, + 32 => SecStringConstant::SecKeyAlgorithmECDSASignatureDigestX962SHA256, + 48 => SecStringConstant::SecKeyAlgorithmECDSASignatureDigestX962SHA384, + 64 => SecStringConstant::SecKeyAlgorithmECDSASignatureDigestX962SHA512, + _ => { + error!( + "Unexpected digested signature input length for ECDSA: {}", + data_len + ); + return Err(()); + } + }; + let algorithm = SECURITY_FRAMEWORK.get_sec_string_constant(algorithm_id)?; + Ok(SignParams::EC(algorithm)) + } + + fn new_rsa_params(params: &Option) -> Result { + let pss_params = match params { + Some(pss_params) => pss_params, + None => { + return Ok(SignParams::RSA( + SECURITY_FRAMEWORK.get_sec_string_constant( + SecStringConstant::SecKeyAlgorithmRSASignatureDigestPKCS1v15Raw, + )?, + )); + } + }; + let algorithm = { + let algorithm_id = match pss_params.hashAlg { + CKM_SHA_1 => SecStringConstant::SecKeyAlgorithmRSASignatureDigestPSSSHA1, + CKM_SHA256 => SecStringConstant::SecKeyAlgorithmRSASignatureDigestPSSSHA256, + CKM_SHA384 => SecStringConstant::SecKeyAlgorithmRSASignatureDigestPSSSHA384, + CKM_SHA512 => SecStringConstant::SecKeyAlgorithmRSASignatureDigestPSSSHA512, + _ => { + error!( + "unsupported algorithm to use with RSA-PSS: {}", + unsafe_packed_field_access!(pss_params.hashAlg) + ); + return Err(()); + } + }; + SECURITY_FRAMEWORK.get_sec_string_constant(algorithm_id)? + }; + Ok(SignParams::RSA(algorithm)) + } + + fn get_algorithm(&self) -> SecKeyAlgorithm { + match self { + SignParams::EC(algorithm) => algorithm.as_concrete_TypeRef(), + SignParams::RSA(algorithm) => algorithm.as_concrete_TypeRef(), + } + } +} + +pub struct Key { + identity: SecIdentity, + class: Vec, + token: Vec, + id: Vec, + private: Vec, + key_type: Vec, + modulus: Option>, + ec_params: Option>, + key_type_enum: KeyType, +} + +impl Key { + fn new(identity: &SecIdentity) -> Result { + let certificate = sec_identity_copy_certificate(identity)?; + let der = sec_certificate_copy_data(&certificate)?; + let id = Sha256::digest(der.bytes()).to_vec(); + let key = SECURITY_FRAMEWORK.sec_certificate_copy_key(&certificate)?; + let key_type: CFString = get_key_attribute(&key, unsafe { kSecAttrKeyType })?; + let key_size_in_bits: CFNumber = get_key_attribute(&key, unsafe { kSecAttrKeySizeInBits })?; + let mut modulus = None; + let mut ec_params = None; + let sec_attr_key_type_ec = SECURITY_FRAMEWORK + .get_sec_string_constant(SecStringConstant::SecAttrKeyTypeECSECPrimeRandom)?; + let (key_type_enum, key_type_attribute) = + if key_type.as_concrete_TypeRef() == unsafe { kSecAttrKeyTypeRSA } { + let public_key = SECURITY_FRAMEWORK.sec_key_copy_external_representation(&key)?; + let modulus_value = read_rsa_modulus(public_key.bytes())?; + modulus = Some(modulus_value); + (KeyType::RSA, CKK_RSA) + } else if key_type == sec_attr_key_type_ec { + // Assume all EC keys are secp256r1, secp384r1, or secp521r1. This + // is wrong, but the API doesn't seem to give us a way to determine + // which curve this key is on. + // This might not matter in practice, because it seems all NSS uses + // this for is to get the signature size. + let key_size_in_bits = match key_size_in_bits.to_i64() { + Some(value) => value, + None => return Err(()), + }; + match key_size_in_bits { + 256 => ec_params = Some(OID_BYTES_SECP256R1.to_vec()), + 384 => ec_params = Some(OID_BYTES_SECP384R1.to_vec()), + 521 => ec_params = Some(OID_BYTES_SECP521R1.to_vec()), + _ => { + error!("unsupported EC key"); + return Err(()); + } + } + let coordinate_width = (key_size_in_bits as usize + 7) / 8; + (KeyType::EC(coordinate_width), CKK_EC) + } else { + error!("unsupported key type"); + return Err(()); + }; + + Ok(Key { + identity: identity.clone(), + class: serialize_uint(CKO_PRIVATE_KEY)?, + token: serialize_uint(CK_TRUE)?, + id, + private: serialize_uint(CK_TRUE)?, + key_type: serialize_uint(key_type_attribute)?, + modulus, + ec_params, + key_type_enum, + }) + } + + fn class(&self) -> &[u8] { + &self.class + } + + fn token(&self) -> &[u8] { + &self.token + } + + pub fn id(&self) -> &[u8] { + &self.id + } + + fn private(&self) -> &[u8] { + &self.private + } + + fn key_type(&self) -> &[u8] { + &self.key_type + } + + fn modulus(&self) -> Option<&[u8]> { + match &self.modulus { + Some(modulus) => Some(modulus.as_slice()), + None => None, + } + } + + fn ec_params(&self) -> Option<&[u8]> { + match &self.ec_params { + Some(ec_params) => Some(ec_params.as_slice()), + None => None, + } + } + + fn matches(&self, attrs: &[(CK_ATTRIBUTE_TYPE, Vec)]) -> bool { + for (attr_type, attr_value) in attrs { + let comparison = match *attr_type { + CKA_CLASS => self.class(), + CKA_TOKEN => self.token(), + CKA_ID => self.id(), + CKA_PRIVATE => self.private(), + CKA_KEY_TYPE => self.key_type(), + CKA_MODULUS => { + if let Some(modulus) = self.modulus() { + modulus + } else { + return false; + } + } + CKA_EC_PARAMS => { + if let Some(ec_params) = self.ec_params() { + ec_params + } else { + return false; + } + } + _ => return false, + }; + if attr_value.as_slice() != comparison { + return false; + } + } + true + } + + fn get_attribute(&self, attribute: CK_ATTRIBUTE_TYPE) -> Option<&[u8]> { + match attribute { + CKA_CLASS => Some(self.class()), + CKA_TOKEN => Some(self.token()), + CKA_ID => Some(self.id()), + CKA_PRIVATE => Some(self.private()), + CKA_KEY_TYPE => Some(self.key_type()), + CKA_MODULUS => self.modulus(), + CKA_EC_PARAMS => self.ec_params(), + _ => None, + } + } + + pub fn get_signature_length( + &self, + data: &[u8], + params: &Option, + ) -> Result { + // Unfortunately we don't have a way of getting the length of a signature without creating + // one. + let dummy_signature_bytes = self.sign(data, params)?; + Ok(dummy_signature_bytes.len()) + } + + // The input data is a hash. What algorithm we use depends on the size of the hash. + pub fn sign( + &self, + data: &[u8], + params: &Option, + ) -> Result, ()> { + let key = sec_identity_copy_private_key(&self.identity)?; + let sign_params = SignParams::new(self.key_type_enum, data.len(), params)?; + let signing_algorithm = sign_params.get_algorithm(); + let data = CFData::from_buffer(data); + let signature = + SECURITY_FRAMEWORK.sec_key_create_signature(&key, signing_algorithm, &data)?; + let signature_value = match self.key_type_enum { + KeyType::EC(coordinate_width) => { + // We need to convert the DER Ecdsa-Sig-Value to the + // concatenation of r and s, the coordinates of the point on + // the curve. r and s must be 0-padded to be coordinate_width + // total bytes. + let (r, s) = read_ec_sig_point(signature.bytes())?; + if r.len() > coordinate_width || s.len() > coordinate_width { + return Err(()); + } + let mut signature_value = Vec::with_capacity(2 * coordinate_width); + let r_padding = vec![0; coordinate_width - r.len()]; + signature_value.extend(r_padding); + signature_value.extend_from_slice(r); + let s_padding = vec![0; coordinate_width - s.len()]; + signature_value.extend(s_padding); + signature_value.extend_from_slice(s); + signature_value + } + KeyType::RSA => signature.bytes().to_vec(), + }; + Ok(signature_value) + } +} + +pub enum Object { + Cert(Cert), + Key(Key), +} + +impl Object { + pub fn matches(&self, slot_type: SlotType, attrs: &[(CK_ATTRIBUTE_TYPE, Vec)]) -> bool { + // The modern/legacy slot distinction in theory enables differentiation + // between keys that are from modules that can use modern cryptography + // (namely EC keys and RSA-PSS signatures) and those that cannot. + // However, the function that would enable this + // (SecKeyIsAlgorithmSupported) causes a password dialog to appear on + // our test machines, so this backend pretends that everything supports + // modern crypto for now. + if slot_type != SlotType::Modern { + return false; + } + match self { + Object::Cert(cert) => cert.matches(attrs), + Object::Key(key) => key.matches(attrs), + } + } + + pub fn get_attribute(&self, attribute: CK_ATTRIBUTE_TYPE) -> Option<&[u8]> { + match self { + Object::Cert(cert) => cert.get_attribute(attribute), + Object::Key(key) => key.get_attribute(attribute), + } + } +} + +pub const SUPPORTED_ATTRIBUTES: &[CK_ATTRIBUTE_TYPE] = &[ + CKA_CLASS, + CKA_TOKEN, + CKA_LABEL, + CKA_ID, + CKA_VALUE, + CKA_ISSUER, + CKA_SERIAL_NUMBER, + CKA_SUBJECT, + CKA_PRIVATE, + CKA_KEY_TYPE, + CKA_MODULUS, + CKA_EC_PARAMS, +]; + +fn get_key_attribute(key: &SecKey, attr: CFStringRef) -> Result { + let attributes: CFDictionary = SECURITY_FRAMEWORK.sec_key_copy_attributes(&key)?; + match attributes.find(attr as *const _) { + Some(value) => Ok((*value).clone()), + None => Err(()), + } +} + +// Given a SecIdentity, attempts to build as much of a path to a trust anchor as possible, gathers +// the CA certificates from that path, and returns them. The purpose of this function is not to +// validate the given certificate but to find CA certificates that gecko may need to do path +// building when filtering client certificates according to the acceptable CA list sent by the +// server during client authentication. +fn get_issuers(identity: &SecIdentity) -> Result, ()> { + let certificate = sec_identity_copy_certificate(identity)?; + let policy = unsafe { SecPolicyCreateSSL(false, std::ptr::null()) }; + if policy.is_null() { + error!("SecPolicyCreateSSL failed"); + return Err(()); + } + let policy = unsafe { SecPolicy::wrap_under_create_rule(policy) }; + let mut trust = std::ptr::null(); + // Each of SecTrustCreateWithCertificates' input arguments can be either single items or an + // array of items. Since we only want to specify one of each, we directly specify the arguments. + let status = unsafe { + SecTrustCreateWithCertificates( + certificate.as_concrete_TypeRef(), + policy.as_concrete_TypeRef(), + &mut trust, + ) + }; + if status != errSecSuccess { + error!("SecTrustCreateWithCertificates failed: {}", status); + return Err(()); + } + if trust.is_null() { + error!("trust is null?"); + return Err(()); + } + let trust = unsafe { SecTrust::wrap_under_create_rule(trust) }; + // Disable AIA fetching so that SecTrustEvaluateWithError doesn't result in network I/O. + let status = unsafe { SecTrustSetNetworkFetchAllowed(trust.as_concrete_TypeRef(), 0) }; + if status != errSecSuccess { + error!("SecTrustSetNetworkFetchAllowed failed: {}", status); + return Err(()); + } + // We ignore the return value here because we don't care if the certificate is trusted or not - + // we're only doing this to build its issuer chain as much as possible. + let _ = SECURITY_FRAMEWORK.sec_trust_evaluate_with_error(&trust)?; + let certificate_count = unsafe { SecTrustGetCertificateCount(trust.as_concrete_TypeRef()) }; + let mut certificates = Vec::with_capacity(certificate_count.try_into().map_err(|_| ())?); + for i in 1..certificate_count { + let certificate = unsafe { SecTrustGetCertificateAtIndex(trust.as_concrete_TypeRef(), i) }; + if certificate.is_null() { + error!("SecTrustGetCertificateAtIndex returned null certificate?"); + continue; + } + let certificate = unsafe { SecCertificate::wrap_under_get_rule(certificate) }; + certificates.push(certificate); + } + Ok(certificates) +} + +pub fn list_objects() -> Vec { + let mut objects = Vec::new(); + let identities = unsafe { + let class_key = CFString::wrap_under_get_rule(kSecClass); + let class_value = CFString::wrap_under_get_rule(kSecClassIdentity); + let return_ref_key = CFString::wrap_under_get_rule(kSecReturnRef); + let return_ref_value = CFBoolean::wrap_under_get_rule(kCFBooleanTrue); + let match_key = CFString::wrap_under_get_rule(kSecMatchLimit); + let match_value = CFString::wrap_under_get_rule(kSecMatchLimitAll); + let vals = vec![ + (class_key.as_CFType(), class_value.as_CFType()), + (return_ref_key.as_CFType(), return_ref_value.as_CFType()), + (match_key.as_CFType(), match_value.as_CFType()), + ]; + let dict = CFDictionary::from_CFType_pairs(&vals); + let mut result = std::ptr::null(); + let status = SecItemCopyMatching(dict.as_CFTypeRef() as CFDictionaryRef, &mut result); + if status != errSecSuccess { + error!("SecItemCopyMatching failed: {}", status); + return objects; + } + if result.is_null() { + debug!("no client certs?"); + return objects; + } + CFArray::::wrap_under_create_rule(result as CFArrayRef) + }; + for identity in identities.get_all_values().iter() { + let identity = unsafe { SecIdentity::wrap_under_get_rule(*identity as SecIdentityRef) }; + let cert = Cert::new_from_identity(&identity); + let key = Key::new(&identity); + if let (Ok(cert), Ok(key)) = (cert, key) { + objects.push(Object::Cert(cert)); + objects.push(Object::Key(key)); + } else { + continue; + } + if let Ok(issuers) = get_issuers(&identity) { + for issuer in issuers { + if let Ok(cert) = Cert::new_from_certificate(&issuer) { + objects.push(Object::Cert(cert)); + } + } + } + } + objects +} diff --git a/security/manager/ssl/osclientcerts/src/backend_windows.rs b/security/manager/ssl/osclientcerts/src/backend_windows.rs new file mode 100644 index 0000000000..a422d2e2bc --- /dev/null +++ b/security/manager/ssl/osclientcerts/src/backend_windows.rs @@ -0,0 +1,933 @@ +/* -*- Mode: rust; rust-indent-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#![allow(non_camel_case_types)] + +use pkcs11::types::*; +use sha2::{Digest, Sha256}; +use std::convert::TryInto; +use std::ffi::{c_void, CStr, CString}; +use std::ops::Deref; +use std::slice; +use winapi::shared::bcrypt::*; +use winapi::shared::minwindef::{DWORD, PBYTE}; +use winapi::um::errhandlingapi::GetLastError; +use winapi::um::ncrypt::*; +use winapi::um::wincrypt::{HCRYPTHASH, HCRYPTPROV, *}; + +use crate::manager::SlotType; +use crate::util::*; + +// winapi has some support for ncrypt.h, but not for this function. +extern "system" { + fn NCryptSignHash( + hKey: NCRYPT_KEY_HANDLE, + pPaddingInfo: *mut c_void, + pbHashValue: PBYTE, + cbHashValue: DWORD, + pbSignature: PBYTE, + cbSignature: DWORD, + pcbResult: *mut DWORD, + dwFlags: DWORD, + ) -> SECURITY_STATUS; +} + +/// Given a `CERT_INFO`, tries to return the bytes of the subject distinguished name as formatted by +/// `CertNameToStrA` using the flag `CERT_SIMPLE_NAME_STR`. This is used as the label for the +/// certificate. +fn get_cert_subject_dn(cert_info: &CERT_INFO) -> Result, ()> { + 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 = vec![0; subject_dn_len as usize]; + let subject_dn_len = unsafe { + CertNameToStrA( + X509_ASN_ENCODING, + &mut cert_info_subject, + CERT_SIMPLE_NAME_STR, + subject_dn_string_bytes.as_mut_ptr() as *mut i8, + subject_dn_string_bytes.len().try_into().map_err(|_| ())?, + ) + }; + if subject_dn_len as usize != subject_dn_string_bytes.len() { + return Err(()); + } + Ok(subject_dn_string_bytes) +} + +/// Represents a certificate for which there exists a corresponding private key. +pub struct Cert { + /// PKCS #11 object class. Will be `CKO_CERTIFICATE`. + class: Vec, + /// Whether or not this is on a token. Will be `CK_TRUE`. + token: Vec, + /// An identifier unique to this certificate. Must be the same as the ID for the private key. + id: Vec, + /// The bytes of a human-readable label for this certificate. Will be the subject DN. + label: Vec, + /// The DER bytes of the certificate. + value: Vec, + /// The DER bytes of the issuer distinguished name of the certificate. + issuer: Vec, + /// The DER bytes of the serial number of the certificate. + serial_number: Vec, + /// The DER bytes of the subject distinguished name of the certificate. + subject: Vec, + /// Which slot this certificate should be exposed on. + slot_type: SlotType, +} + +impl Cert { + fn new(cert_context: PCCERT_CONTEXT) -> Result { + let cert = unsafe { &*cert_context }; + let cert_info = unsafe { &*cert.pCertInfo }; + let value = + unsafe { slice::from_raw_parts(cert.pbCertEncoded, cert.cbCertEncoded as usize) }; + let value = value.to_vec(); + let id = Sha256::digest(&value).to_vec(); + let label = get_cert_subject_dn(&cert_info)?; + let issuer = unsafe { + slice::from_raw_parts(cert_info.Issuer.pbData, cert_info.Issuer.cbData as usize) + }; + let issuer = issuer.to_vec(); + let serial_number = read_encoded_serial_number(&value)?; + let subject = unsafe { + slice::from_raw_parts(cert_info.Subject.pbData, cert_info.Subject.cbData as usize) + }; + let subject = subject.to_vec(); + Ok(Cert { + class: serialize_uint(CKO_CERTIFICATE)?, + token: serialize_uint(CK_TRUE)?, + id, + label, + value, + issuer, + serial_number, + subject, + slot_type: SlotType::Modern, + }) + } + + fn class(&self) -> &[u8] { + &self.class + } + + fn token(&self) -> &[u8] { + &self.token + } + + pub fn id(&self) -> &[u8] { + &self.id + } + + fn label(&self) -> &[u8] { + &self.label + } + + fn value(&self) -> &[u8] { + &self.value + } + + fn issuer(&self) -> &[u8] { + &self.issuer + } + + fn serial_number(&self) -> &[u8] { + &self.serial_number + } + + fn subject(&self) -> &[u8] { + &self.subject + } + + fn matches(&self, slot_type: SlotType, attrs: &[(CK_ATTRIBUTE_TYPE, Vec)]) -> 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 { + let mut key_handle = 0; + let mut key_spec = 0; + let mut must_free = 0; + unsafe { + if CryptAcquireCertificatePrivateKey( + **cert, + CRYPT_ACQUIRE_PREFER_NCRYPT_KEY_FLAG, + std::ptr::null_mut(), + &mut key_handle, + &mut key_spec, + &mut must_free, + ) != 1 + { + error!( + "CryptAcquireCertificatePrivateKey failed: 0x{:x}", + GetLastError() + ); + return Err(()); + } + } + if must_free == 0 { + error!("CryptAcquireCertificatePrivateKey returned shared key handle"); + return Err(()); + } + if key_spec == CERT_NCRYPT_KEY_SPEC { + Ok(KeyHandle::NCrypt(key_handle as NCRYPT_KEY_HANDLE)) + } else { + Ok(KeyHandle::CryptoAPI(key_handle as HCRYPTPROV, key_spec)) + } + } + + fn sign( + &self, + data: &[u8], + params: &Option, + do_signature: bool, + key_type: KeyType, + ) -> Result, ()> { + 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, + do_signature: bool, + key_type: KeyType, +) -> Result, ()> { + let mut sign_params = SignParams::new(key_type, params)?; + let params_ptr = sign_params.params_ptr(); + let flags = sign_params.flags(); + let mut data = data.to_vec(); + let mut signature_len = 0; + // We call NCryptSignHash twice: the first time to get the size of the buffer we need to + // allocate and then again to actually sign the data, if `do_signature` is `true`. + let status = unsafe { + NCryptSignHash( + *ncrypt_handle, + params_ptr, + data.as_mut_ptr(), + data.len().try_into().map_err(|_| ())?, + std::ptr::null_mut(), + 0, + &mut signature_len, + flags, + ) + }; + // 0 is "ERROR_SUCCESS" (but "ERROR_SUCCESS" is unsigned, whereas SECURITY_STATUS is signed) + if status != 0 { + error!( + "NCryptSignHash failed trying to get signature buffer length, {}", + status + ); + return Err(()); + } + let mut signature = vec![0; signature_len as usize]; + if !do_signature { + return Ok(signature); + } + let mut final_signature_len = signature_len; + let status = unsafe { + NCryptSignHash( + *ncrypt_handle, + params_ptr, + data.as_mut_ptr(), + data.len().try_into().map_err(|_| ())?, + signature.as_mut_ptr(), + signature_len, + &mut final_signature_len, + flags, + ) + }; + if status != 0 { + error!("NCryptSignHash failed signing data {}", status); + return Err(()); + } + if final_signature_len != signature_len { + error!( + "NCryptSignHash: inconsistent signature lengths? {} != {}", + final_signature_len, signature_len + ); + return Err(()); + } + Ok(signature) +} + +fn sign_cryptoapi( + hcryptprov: &HCRYPTPROV, + key_spec: &DWORD, + data: &[u8], + params: &Option, + do_signature: bool, +) -> Result, ()> { + if params.is_some() { + error!("non-None signature params cannot be used with CryptoAPI"); + return Err(()); + } + // data will be an encoded DigestInfo, which specifies the hash algorithm and bytes of the hash + // to sign. However, CryptoAPI requires directly specifying the bytes of the hash, so it must + // be extracted first. + let hash_bytes = read_digest(data)?; + let hash = HCryptHash::new(hcryptprov, hash_bytes)?; + let mut signature_len = 0; + if unsafe { + CryptSignHashW( + *hash, + *key_spec, + std::ptr::null_mut(), + 0, + std::ptr::null_mut(), + &mut signature_len, + ) + } != 1 + { + error!( + "CryptSignHash failed trying to get signature buffer length: 0x{:x}", + unsafe { GetLastError() } + ); + return Err(()); + } + let mut signature = vec![0; signature_len as usize]; + if !do_signature { + return Ok(signature); + } + let mut final_signature_len = signature_len; + if unsafe { + CryptSignHashW( + *hash, + *key_spec, + std::ptr::null_mut(), + 0, + signature.as_mut_ptr(), + &mut final_signature_len, + ) + } != 1 + { + error!("CryptSignHash failed signing data: 0x{:x}", unsafe { + GetLastError() + }); + return Err(()); + } + if final_signature_len != signature_len { + error!( + "CryptSignHash: inconsistent signature lengths? {} != {}", + final_signature_len, signature_len + ); + return Err(()); + } + // CryptoAPI returns the signature with the most significant byte last (little-endian), + // whereas PKCS#11 expects the most significant byte first (big-endian). + signature.reverse(); + Ok(signature) +} + +struct HCryptHash(HCRYPTHASH); + +impl HCryptHash { + fn new(hcryptprov: &HCRYPTPROV, hash_bytes: &[u8]) -> Result { + let alg = match hash_bytes.len() { + 20 => CALG_SHA1, + 32 => CALG_SHA_256, + 48 => CALG_SHA_384, + 64 => CALG_SHA_512, + _ => { + error!( + "HCryptHash::new: invalid hash of length {}", + hash_bytes.len() + ); + return Err(()); + } + }; + let mut hash: HCRYPTHASH = 0; + if unsafe { CryptCreateHash(*hcryptprov, alg, 0, 0, &mut hash) } != 1 { + error!("CryptCreateHash failed: 0x{:x}", unsafe { GetLastError() }); + return Err(()); + } + if unsafe { CryptSetHashParam(hash, HP_HASHVAL, hash_bytes.as_ptr(), 0) } != 1 { + error!("CryptSetHashParam failed: 0x{:x}", unsafe { + GetLastError() + }); + return Err(()); + } + Ok(HCryptHash(hash)) + } +} + +impl Drop for HCryptHash { + fn drop(&mut self) { + unsafe { + CryptDestroyHash(self.0); + } + } +} + +impl Deref for HCryptHash { + type Target = HCRYPTHASH; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +// In some cases, the ncrypt API takes a pointer to a null-terminated wide-character string as a way +// of specifying an algorithm. The "right" way to do this would be to take the corresponding +// &'static str constant provided by the winapi crate, create an OsString from it, encode it as wide +// characters, and collect it into a Vec. 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) -> Result { + // EC is easy, so handle that first. + match key_type { + KeyType::EC => return Ok(SignParams::EC), + KeyType::RSA => {} + } + // If `params` is `Some`, we're doing RSA-PSS. If it is `None`, we're doing RSA-PKCS1. + let pss_params = match params { + Some(pss_params) => pss_params, + None => { + // The hash algorithm should be encoded in the data to be signed, so we don't have to + // (and don't want to) specify a particular algorithm here. + return Ok(SignParams::RSA_PKCS1(BCRYPT_PKCS1_PADDING_INFO { + pszAlgId: std::ptr::null(), + })); + } + }; + let algorithm_string = match pss_params.hashAlg { + CKM_SHA_1 => SHA1_ALGORITHM_STRING, + CKM_SHA256 => SHA256_ALGORITHM_STRING, + CKM_SHA384 => SHA384_ALGORITHM_STRING, + CKM_SHA512 => SHA512_ALGORITHM_STRING, + _ => { + error!( + "unsupported algorithm to use with RSA-PSS: {}", + unsafe_packed_field_access!(pss_params.hashAlg) + ); + return Err(()); + } + }; + Ok(SignParams::RSA_PSS(BCRYPT_PSS_PADDING_INFO { + pszAlgId: algorithm_string.as_ptr(), + cbSalt: pss_params.sLen, + })) + } + + fn params_ptr(&mut self) -> *mut std::ffi::c_void { + match self { + SignParams::EC => std::ptr::null_mut(), + SignParams::RSA_PKCS1(params) => { + params as *mut BCRYPT_PKCS1_PADDING_INFO as *mut std::ffi::c_void + } + SignParams::RSA_PSS(params) => { + params as *mut BCRYPT_PSS_PADDING_INFO as *mut std::ffi::c_void + } + } + } + + fn flags(&self) -> u32 { + match self { + &SignParams::EC => 0, + &SignParams::RSA_PKCS1(_) => NCRYPT_PAD_PKCS1_FLAG, + &SignParams::RSA_PSS(_) => NCRYPT_PAD_PSS_FLAG, + } + } +} + +/// A helper enum to identify a private key's type. We support EC and RSA. +#[derive(Clone, Copy, Debug)] +pub enum KeyType { + EC, + RSA, +} + +/// Represents a private key for which there exists a corresponding certificate. +pub struct Key { + /// A handle on the OS mechanism that represents the certificate for this key. + cert: CertContext, + /// PKCS #11 object class. Will be `CKO_PRIVATE_KEY`. + class: Vec, + /// Whether or not this is on a token. Will be `CK_TRUE`. + token: Vec, + /// An identifier unique to this key. Must be the same as the ID for the certificate. + id: Vec, + /// Whether or not this key is "private" (can it be exported?). Will be CK_TRUE (it can't be + /// exported). + private: Vec, + /// PKCS #11 key type. Will be `CKK_EC` for EC, and `CKK_RSA` for RSA. + key_type: Vec, + /// If this is an RSA key, this is the value of the modulus as an unsigned integer. + modulus: Option>, + /// If this is an EC key, this is the DER bytes of the OID identifying the curve the key is on. + ec_params: Option>, + /// An enum identifying this key's type. + key_type_enum: KeyType, + /// Which slot this key should be exposed on. + slot_type: SlotType, +} + +impl Key { + fn new(cert_context: PCCERT_CONTEXT) -> Result { + let cert = unsafe { *cert_context }; + let cert_der = + unsafe { slice::from_raw_parts(cert.pbCertEncoded, cert.cbCertEncoded as usize) }; + let id = Sha256::digest(cert_der).to_vec(); + let id = id.to_vec(); + let cert_info = unsafe { &*cert.pCertInfo }; + let mut modulus = None; + let mut ec_params = None; + let spki = &cert_info.SubjectPublicKeyInfo; + let algorithm_oid = unsafe { CStr::from_ptr(spki.Algorithm.pszObjId) } + .to_str() + .map_err(|_| ())?; + let (key_type_enum, key_type_attribute) = if algorithm_oid == szOID_RSA_RSA { + if spki.PublicKey.cUnusedBits != 0 { + return Err(()); + } + let public_key_bytes = unsafe { + std::slice::from_raw_parts(spki.PublicKey.pbData, spki.PublicKey.cbData as usize) + }; + let modulus_value = read_rsa_modulus(public_key_bytes)?; + modulus = Some(modulus_value); + (KeyType::RSA, CKK_RSA) + } else if algorithm_oid == szOID_ECC_PUBLIC_KEY { + let params = &spki.Algorithm.Parameters; + ec_params = Some( + unsafe { std::slice::from_raw_parts(params.pbData, params.cbData as usize) } + .to_vec(), + ); + (KeyType::EC, CKK_EC) + } else { + return Err(()); + }; + let cert = CertContext::new(cert_context); + Ok(Key { + cert, + class: serialize_uint(CKO_PRIVATE_KEY)?, + token: serialize_uint(CK_TRUE)?, + id, + private: serialize_uint(CK_TRUE)?, + key_type: serialize_uint(key_type_attribute)?, + modulus, + ec_params, + key_type_enum, + slot_type: SlotType::Modern, + }) + } + + fn class(&self) -> &[u8] { + &self.class + } + + fn token(&self) -> &[u8] { + &self.token + } + + pub fn id(&self) -> &[u8] { + &self.id + } + + fn private(&self) -> &[u8] { + &self.private + } + + fn key_type(&self) -> &[u8] { + &self.key_type + } + + fn modulus(&self) -> Option<&[u8]> { + match &self.modulus { + Some(modulus) => Some(modulus.as_slice()), + None => None, + } + } + + fn ec_params(&self) -> Option<&[u8]> { + match &self.ec_params { + Some(ec_params) => Some(ec_params.as_slice()), + None => None, + } + } + + fn matches(&self, slot_type: SlotType, attrs: &[(CK_ATTRIBUTE_TYPE, Vec)]) -> bool { + if slot_type != self.slot_type { + return false; + } + for (attr_type, attr_value) in attrs { + let comparison = match *attr_type { + CKA_CLASS => self.class(), + CKA_TOKEN => self.token(), + CKA_ID => self.id(), + CKA_PRIVATE => self.private(), + CKA_KEY_TYPE => self.key_type(), + CKA_MODULUS => { + if let Some(modulus) = self.modulus() { + modulus + } else { + return false; + } + } + CKA_EC_PARAMS => { + if let Some(ec_params) = self.ec_params() { + ec_params + } else { + return false; + } + } + _ => return false, + }; + if attr_value.as_slice() != comparison { + return false; + } + } + true + } + + fn get_attribute(&self, attribute: CK_ATTRIBUTE_TYPE) -> Option<&[u8]> { + match attribute { + CKA_CLASS => Some(self.class()), + CKA_TOKEN => Some(self.token()), + CKA_ID => Some(self.id()), + CKA_PRIVATE => Some(self.private()), + CKA_KEY_TYPE => Some(self.key_type()), + CKA_MODULUS => self.modulus(), + CKA_EC_PARAMS => self.ec_params(), + _ => None, + } + } + + pub fn get_signature_length( + &self, + data: &[u8], + params: &Option, + ) -> Result { + match self.sign_internal(data, params, false) { + Ok(dummy_signature_bytes) => Ok(dummy_signature_bytes.len()), + Err(()) => Err(()), + } + } + + pub fn sign( + &self, + data: &[u8], + params: &Option, + ) -> Result, ()> { + self.sign_internal(data, params, true) + } + + /// data: the data to sign + /// do_signature: if true, actually perform the signature. Otherwise, return a `Vec` of the + /// length the signature would be, if performed. + fn sign_internal( + &self, + data: &[u8], + params: &Option, + do_signature: bool, + ) -> Result, ()> { + // Acquiring a handle on the key can cause the OS to show some UI to the user, so we do this + // as late as possible (i.e. here). + let key = KeyHandle::from_cert(&self.cert)?; + key.sign(data, params, do_signature, self.key_type_enum) + } +} + +/// A helper enum that represents the two types of PKCS #11 objects we support: certificates and +/// keys. +pub enum Object { + Cert(Cert), + Key(Key), +} + +impl Object { + pub fn matches(&self, slot_type: SlotType, attrs: &[(CK_ATTRIBUTE_TYPE, Vec)]) -> bool { + match self { + Object::Cert(cert) => cert.matches(slot_type, attrs), + Object::Key(key) => key.matches(slot_type, attrs), + } + } + + pub fn get_attribute(&self, attribute: CK_ATTRIBUTE_TYPE) -> Option<&[u8]> { + match self { + Object::Cert(cert) => cert.get_attribute(attribute), + Object::Key(key) => key.get_attribute(attribute), + } + } +} + +struct CertStore { + handle: HCERTSTORE, +} + +impl Drop for CertStore { + fn drop(&mut self) { + if !self.handle.is_null() { + unsafe { + CertCloseStore(self.handle, 0); + } + } + } +} + +impl Deref for CertStore { + type Target = HCERTSTORE; + + fn deref(&self) -> &Self::Target { + &self.handle + } +} + +impl CertStore { + fn new(handle: HCERTSTORE) -> CertStore { + CertStore { handle } + } +} + +pub const SUPPORTED_ATTRIBUTES: &[CK_ATTRIBUTE_TYPE] = &[ + CKA_CLASS, + CKA_TOKEN, + CKA_LABEL, + CKA_ID, + CKA_VALUE, + CKA_ISSUER, + CKA_SERIAL_NUMBER, + CKA_SUBJECT, + CKA_PRIVATE, + CKA_KEY_TYPE, + CKA_MODULUS, + CKA_EC_PARAMS, +]; + +// Given a pointer to a CERT_CHAIN_CONTEXT, enumerates each chain in the context and each element +// in each chain to gather every CERT_CONTEXT pointed to by the CERT_CHAIN_CONTEXT. +// https://docs.microsoft.com/en-us/windows/win32/api/wincrypt/ns-wincrypt-cert_chain_context says +// that the 0th element of the 0th chain will be the end-entity certificate. This certificate (if +// present), will be the 0th element of the returned Vec. +fn gather_cert_contexts(cert_chain_context: *const CERT_CHAIN_CONTEXT) -> Vec<*const CERT_CONTEXT> { + let mut cert_contexts = Vec::new(); + if cert_chain_context.is_null() { + return cert_contexts; + } + let cert_chain_context = unsafe { &*cert_chain_context }; + let cert_chains = unsafe { + std::slice::from_raw_parts( + cert_chain_context.rgpChain, + cert_chain_context.cChain as usize, + ) + }; + for cert_chain in cert_chains { + // First dereference the borrow. + let cert_chain = *cert_chain; + if cert_chain.is_null() { + continue; + } + // Then dereference the pointer. + let cert_chain = unsafe { &*cert_chain }; + let chain_elements = unsafe { + std::slice::from_raw_parts(cert_chain.rgpElement, cert_chain.cElement as usize) + }; + for chain_element in chain_elements { + let chain_element = *chain_element; // dereference borrow + if chain_element.is_null() { + continue; + } + let chain_element = unsafe { &*chain_element }; // dereference pointer + cert_contexts.push(chain_element.pCertContext); + } + } + cert_contexts +} + +/// Attempts to enumerate certificates with private keys exposed by the OS. Currently only looks in +/// the "My" cert store of the current user. In the future this may look in more locations. +pub fn list_objects() -> Vec { + let mut objects = Vec::new(); + let location_flags = CERT_SYSTEM_STORE_CURRENT_USER // TODO: loop over multiple locations + | CERT_STORE_OPEN_EXISTING_FLAG + | CERT_STORE_READONLY_FLAG; + let store_name = match CString::new("My") { + Ok(store_name) => store_name, + Err(null_error) => { + error!("CString::new given input with a null byte: {}", null_error); + return objects; + } + }; + let store = CertStore::new(unsafe { + CertOpenStore( + CERT_STORE_PROV_SYSTEM_REGISTRY_A, + 0, + 0, + location_flags, + store_name.as_ptr() as *const winapi::ctypes::c_void, + ) + }); + if store.is_null() { + error!("CertOpenStore failed"); + return objects; + } + let find_params = CERT_CHAIN_FIND_ISSUER_PARA { + cbSize: std::mem::size_of::() as u32, + pszUsageIdentifier: std::ptr::null(), + dwKeySpec: 0, + dwAcquirePrivateKeyFlags: 0, + cIssuer: 0, + rgIssuer: std::ptr::null_mut(), + pfnFindCallback: None, + pvFindArg: std::ptr::null_mut(), + pdwIssuerChainIndex: std::ptr::null_mut(), + pdwIssuerElementIndex: std::ptr::null_mut(), + }; + let mut cert_chain_context: PCCERT_CHAIN_CONTEXT = std::ptr::null_mut(); + loop { + // CertFindChainInStore finds all certificates with private keys in the store. It also + // attempts to build a verified certificate chain to a trust anchor for each certificate. + // We gather and hold onto these extra certificates so that gecko can use them when + // filtering potential client certificates according to the acceptable CAs list sent by + // servers when they request client certificates. + cert_chain_context = unsafe { + CertFindChainInStore( + *store, + X509_ASN_ENCODING, + CERT_CHAIN_FIND_BY_ISSUER_CACHE_ONLY_FLAG + | CERT_CHAIN_FIND_BY_ISSUER_CACHE_ONLY_URL_FLAG, + CERT_CHAIN_FIND_BY_ISSUER, + &find_params as *const CERT_CHAIN_FIND_ISSUER_PARA as *const winapi::ctypes::c_void, + cert_chain_context, + ) + }; + if cert_chain_context.is_null() { + break; + } + let cert_contexts = gather_cert_contexts(cert_chain_context); + // The 0th CERT_CONTEXT is the end-entity (i.e. the certificate with the private key we're + // after). + match cert_contexts.get(0) { + Some(cert_context) => { + let key = match Key::new(*cert_context) { + Ok(key) => key, + Err(()) => continue, + }; + let cert = match Cert::new(*cert_context) { + Ok(cert) => cert, + Err(()) => continue, + }; + objects.push(Object::Cert(cert)); + objects.push(Object::Key(key)); + } + None => {} + }; + for cert_context in cert_contexts.iter().skip(1) { + if let Ok(cert) = Cert::new(*cert_context) { + objects.push(Object::Cert(cert)); + } + } + } + objects +} diff --git a/security/manager/ssl/osclientcerts/src/bindings_macos.rs b/security/manager/ssl/osclientcerts/src/bindings_macos.rs new file mode 100644 index 0000000000..14aff193b3 --- /dev/null +++ b/security/manager/ssl/osclientcerts/src/bindings_macos.rs @@ -0,0 +1,58 @@ +/* -*- Mode: rust; rust-indent-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// This was originally generated by rust-bindgen at build time. Later in +// development it became clear that using bindgen for this library as part of +// mozilla-central would be difficult (if not impossible). So, this was +// converted to a static file and unused declarations were removed. Also, +// intermediate types added by rust-bindgen were removed for clarity. + +pub type OSStatus = i32; +pub const errSecSuccess: OSStatus = 0; + +pub type SecKeyAlgorithm = CFStringRef; + +extern "C" { + // Available starting macOS 10.3 + pub fn SecCertificateGetTypeID() -> CFTypeID; + pub fn SecTrustCreateWithCertificates( + certificates: SecCertificateRef, + policies: SecPolicyRef, + trust: *mut SecTrustRef, + ) -> OSStatus; + pub fn SecIdentityGetTypeID() -> CFTypeID; + pub fn SecIdentityCopyCertificate( + identityRef: SecIdentityRef, + certificateRef: *mut SecCertificateRef, + ) -> OSStatus; + pub fn SecIdentityCopyPrivateKey( + identityRef: SecIdentityRef, + privateKeyRef: *mut SecKeyRef, + ) -> OSStatus; + pub fn SecKeyGetTypeID() -> CFTypeID; + pub fn SecPolicyGetTypeID() -> CFTypeID; + pub fn SecTrustGetTypeID() -> CFTypeID; + + // Available starting macOS 10.6 + pub fn SecCertificateCopyData(certificate: SecCertificateRef) -> CFDataRef; + pub fn SecCertificateCopySubjectSummary(certificate: SecCertificateRef) -> CFStringRef; + pub fn SecItemCopyMatching(query: CFDictionaryRef, result: *mut CFTypeRef) -> OSStatus; + pub fn SecPolicyCreateSSL(server: bool, hostname: CFStringRef) -> SecPolicyRef; + pub static kSecClass: CFStringRef; + pub static kSecAttrKeyType: CFStringRef; + pub static kSecAttrKeySizeInBits: CFStringRef; + pub static kSecMatchLimit: CFStringRef; + pub static kSecMatchLimitAll: CFStringRef; + pub static kSecReturnRef: CFStringRef; + + // Available starting macOS 10.7 + pub fn SecTrustGetCertificateAtIndex(trust: SecTrustRef, ix: CFIndex) -> SecCertificateRef; + pub fn SecTrustGetCertificateCount(trust: SecTrustRef) -> CFIndex; + pub static kSecClassIdentity: CFStringRef; + pub static kSecAttrKeyTypeRSA: CFStringRef; + + // Available starting macOS 10.9 + pub fn SecTrustSetNetworkFetchAllowed(trust: SecTrustRef, allowFetch: Boolean) -> OSStatus; +} diff --git a/security/manager/ssl/osclientcerts/src/lib.rs b/security/manager/ssl/osclientcerts/src/lib.rs new file mode 100644 index 0000000000..f53f45f142 --- /dev/null +++ b/security/manager/ssl/osclientcerts/src/lib.rs @@ -0,0 +1,1208 @@ +/* -*- Mode: rust; rust-indent-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#![allow(non_snake_case)] + +extern crate byteorder; +#[cfg(target_os = "macos")] +#[macro_use] +extern crate core_foundation; +extern crate env_logger; +#[macro_use] +extern crate lazy_static; +#[cfg(target_os = "macos")] +extern crate libloading; +#[macro_use] +extern crate log; +extern crate pkcs11; +#[cfg(target_os = "macos")] +#[macro_use] +extern crate rental; +extern crate sha2; +#[cfg(target_os = "windows")] +extern crate winapi; + +use pkcs11::types::*; +use std::sync::Mutex; +use std::thread; + +mod manager; +#[macro_use] +mod util; +#[cfg(target_os = "macos")] +mod backend_macos; +#[cfg(target_os = "windows")] +mod backend_windows; + +use manager::{ManagerProxy, SlotType}; + +lazy_static! { + /// The singleton `ManagerProxy` that handles state with respect to PKCS #11. Only one thread + /// may use it at a time, but there is no restriction on which threads may use it. However, as + /// OS APIs being used are not necessarily thread-safe (e.g. they may be using + /// thread-local-storage), the `ManagerProxy` forwards calls from any thread to a single thread + /// where the real `Manager` does the actual work. + static ref MANAGER_PROXY: Mutex> = Mutex::new(None); +} + +// Obtaining a handle on the manager proxy is a two-step process. First the mutex must be locked, +// which (if successful), results in a mutex guard object. We must then get a mutable refence to the +// underlying manager proxy (if set - otherwise we return an error). This can't happen all in one +// macro without dropping a reference that needs to live long enough for this to be safe. In +// practice, this looks like: +// let mut manager_guard = try_to_get_manager_guard!(); +// let manager = manager_guard_to_manager!(manager_guard); +macro_rules! try_to_get_manager_guard { + () => { + match MANAGER_PROXY.lock() { + Ok(maybe_manager_proxy) => maybe_manager_proxy, + Err(poison_error) => { + log_with_thread_id!( + error, + "previous thread panicked acquiring manager lock: {}", + poison_error + ); + return CKR_DEVICE_ERROR; + } + } + }; +} + +macro_rules! manager_guard_to_manager { + ($manager_guard:ident) => { + match $manager_guard.as_mut() { + Some(manager_proxy) => manager_proxy, + None => { + log_with_thread_id!(error, "manager expected to be set, but it is not"); + return CKR_DEVICE_ERROR; + } + } + }; +} + +// Helper macro to prefix log messages with the current thread ID. +macro_rules! log_with_thread_id { + ($log_level:ident, $($message:expr),*) => { + let message = format!($($message),*); + $log_level!("{:?} {}", thread::current().id(), message); + }; +} + +/// This gets called to initialize the module. For this implementation, this consists of +/// instantiating the `ManagerProxy`. +extern "C" fn C_Initialize(_pInitArgs: CK_C_INITIALIZE_ARGS_PTR) -> CK_RV { + // This will fail if this has already been called, but this isn't a problem because either way, + // logging has been initialized. + let _ = env_logger::try_init(); + let mut manager_guard = try_to_get_manager_guard!(); + match manager_guard.replace(ManagerProxy::new()) { + Some(_unexpected_previous_manager) => { + #[cfg(target_os = "macos")] + { + log_with_thread_id!(info, "C_Initialize: manager previously set (this is expected on macOS - replacing it)"); + } + #[cfg(target_os = "windows")] + { + log_with_thread_id!(warn, "C_Initialize: manager unexpectedly previously set (bravely continuing by replacing it)"); + } + } + None => {} + } + log_with_thread_id!(debug, "C_Initialize: CKR_OK"); + CKR_OK +} + +extern "C" fn C_Finalize(_pReserved: CK_VOID_PTR) -> CK_RV { + let mut manager_guard = try_to_get_manager_guard!(); + let manager = manager_guard_to_manager!(manager_guard); + match manager.stop() { + Ok(()) => { + log_with_thread_id!(debug, "C_Finalize: CKR_OK"); + CKR_OK + } + Err(()) => { + log_with_thread_id!(error, "C_Finalize: CKR_DEVICE_ERROR"); + CKR_DEVICE_ERROR + } + } +} + +// The specification mandates that these strings be padded with spaces to the appropriate length. +// Since the length of fixed-size arrays in rust is part of the type, the compiler enforces that +// these byte strings are of the correct length. +const MANUFACTURER_ID_BYTES: &[u8; 32] = b"Mozilla Corporation "; +const LIBRARY_DESCRIPTION_BYTES: &[u8; 32] = b"OS Client Cert Module "; + +/// This gets called to gather some information about the module. In particular, this implementation +/// supports (portions of) cryptoki (PKCS #11) version 2.2. +extern "C" fn C_GetInfo(pInfo: CK_INFO_PTR) -> CK_RV { + if pInfo.is_null() { + log_with_thread_id!(error, "C_GetInfo: CKR_ARGUMENTS_BAD"); + return CKR_ARGUMENTS_BAD; + } + log_with_thread_id!(debug, "C_GetInfo: CKR_OK"); + let mut info = CK_INFO::default(); + info.cryptokiVersion.major = 2; + info.cryptokiVersion.minor = 2; + info.manufacturerID = *MANUFACTURER_ID_BYTES; + info.libraryDescription = *LIBRARY_DESCRIPTION_BYTES; + unsafe { + *pInfo = info; + } + CKR_OK +} + +/// This module has two slots. +const SLOT_COUNT: CK_ULONG = 2; +/// The slot with ID 1 supports modern mechanisms like RSA-PSS. +const SLOT_ID_MODERN: CK_SLOT_ID = 1; +/// The slot with ID 2 only supports legacy mechanisms. +const SLOT_ID_LEGACY: CK_SLOT_ID = 2; + +/// This gets called twice: once with a null `pSlotList` to get the number of slots (returned via +/// `pulCount`) and a second time to get the ID for each slot. +extern "C" fn C_GetSlotList( + _tokenPresent: CK_BBOOL, + pSlotList: CK_SLOT_ID_PTR, + pulCount: CK_ULONG_PTR, +) -> CK_RV { + if pulCount.is_null() { + log_with_thread_id!(error, "C_GetSlotList: CKR_ARGUMENTS_BAD"); + return CKR_ARGUMENTS_BAD; + } + if !pSlotList.is_null() { + if unsafe { *pulCount } < SLOT_COUNT { + log_with_thread_id!(error, "C_GetSlotList: CKR_BUFFER_TOO_SMALL"); + return CKR_BUFFER_TOO_SMALL; + } + unsafe { + *pSlotList = SLOT_ID_MODERN; + *pSlotList.offset(1) = SLOT_ID_LEGACY; + } + }; + unsafe { + *pulCount = SLOT_COUNT; + } + log_with_thread_id!(debug, "C_GetSlotList: CKR_OK"); + CKR_OK +} + +const SLOT_DESCRIPTION_MODERN_BYTES: &[u8; 64] = + b"OS Client Cert Slot (Modern) "; +const SLOT_DESCRIPTION_LEGACY_BYTES: &[u8; 64] = + b"OS Client Cert Slot (Legacy) "; + +/// This gets called to obtain information about slots. In this implementation, the tokens are +/// always present in the slots. +extern "C" fn C_GetSlotInfo(slotID: CK_SLOT_ID, pInfo: CK_SLOT_INFO_PTR) -> CK_RV { + if (slotID != SLOT_ID_MODERN && slotID != SLOT_ID_LEGACY) || pInfo.is_null() { + log_with_thread_id!(error, "C_GetSlotInfo: CKR_ARGUMENTS_BAD"); + return CKR_ARGUMENTS_BAD; + } + let description = if slotID == SLOT_ID_MODERN { + SLOT_DESCRIPTION_MODERN_BYTES + } else { + SLOT_DESCRIPTION_LEGACY_BYTES + }; + let slot_info = CK_SLOT_INFO { + slotDescription: *description, + manufacturerID: *MANUFACTURER_ID_BYTES, + flags: CKF_TOKEN_PRESENT, + hardwareVersion: CK_VERSION::default(), + firmwareVersion: CK_VERSION::default(), + }; + unsafe { + *pInfo = slot_info; + } + log_with_thread_id!(debug, "C_GetSlotInfo: CKR_OK"); + CKR_OK +} + +const TOKEN_LABEL_MODERN_BYTES: &[u8; 32] = b"OS Client Cert Token (Modern) "; +const TOKEN_LABEL_LEGACY_BYTES: &[u8; 32] = b"OS Client Cert Token (Legacy) "; +const TOKEN_MODEL_BYTES: &[u8; 16] = b"osclientcerts "; +const TOKEN_SERIAL_NUMBER_BYTES: &[u8; 16] = b"0000000000000000"; + +/// This gets called to obtain some information about tokens. This implementation has two slots, +/// so it has two tokens. This information is primarily for display purposes. +extern "C" fn C_GetTokenInfo(slotID: CK_SLOT_ID, pInfo: CK_TOKEN_INFO_PTR) -> CK_RV { + if (slotID != SLOT_ID_MODERN && slotID != SLOT_ID_LEGACY) || pInfo.is_null() { + log_with_thread_id!(error, "C_GetTokenInfo: CKR_ARGUMENTS_BAD"); + return CKR_ARGUMENTS_BAD; + } + let mut token_info = CK_TOKEN_INFO::default(); + let label = if slotID == SLOT_ID_MODERN { + TOKEN_LABEL_MODERN_BYTES + } else { + TOKEN_LABEL_LEGACY_BYTES + }; + token_info.label = *label; + token_info.manufacturerID = *MANUFACTURER_ID_BYTES; + token_info.model = *TOKEN_MODEL_BYTES; + token_info.serialNumber = *TOKEN_SERIAL_NUMBER_BYTES; + unsafe { + *pInfo = token_info; + } + log_with_thread_id!(debug, "C_GetTokenInfo: CKR_OK"); + CKR_OK +} + +/// This gets called to determine what mechanisms a slot supports. The modern slot supports ECDSA, +/// RSA PKCS, and RSA PSS. The legacy slot only supports RSA PKCS. +extern "C" fn C_GetMechanismList( + slotID: CK_SLOT_ID, + pMechanismList: CK_MECHANISM_TYPE_PTR, + pulCount: CK_ULONG_PTR, +) -> CK_RV { + if (slotID != SLOT_ID_MODERN && slotID != SLOT_ID_LEGACY) || pulCount.is_null() { + log_with_thread_id!(error, "C_GetMechanismList: CKR_ARGUMENTS_BAD"); + return CKR_ARGUMENTS_BAD; + } + let mechanisms = if slotID == SLOT_ID_MODERN { + vec![CKM_ECDSA, CKM_RSA_PKCS, CKM_RSA_PKCS_PSS] + } else { + vec![CKM_RSA_PKCS] + }; + if !pMechanismList.is_null() { + if unsafe { *pulCount as usize } < mechanisms.len() { + log_with_thread_id!(error, "C_GetMechanismList: CKR_ARGUMENTS_BAD"); + return CKR_ARGUMENTS_BAD; + } + for i in 0..mechanisms.len() { + unsafe { + *pMechanismList.offset(i as isize) = mechanisms[i]; + } + } + } + unsafe { + *pulCount = mechanisms.len() as CK_ULONG; + } + log_with_thread_id!(debug, "C_GetMechanismList: CKR_OK"); + CKR_OK +} + +extern "C" fn C_GetMechanismInfo( + _slotID: CK_SLOT_ID, + _type: CK_MECHANISM_TYPE, + _pInfo: CK_MECHANISM_INFO_PTR, +) -> CK_RV { + log_with_thread_id!(error, "C_GetMechanismInfo: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_InitToken( + _slotID: CK_SLOT_ID, + _pPin: CK_UTF8CHAR_PTR, + _ulPinLen: CK_ULONG, + _pLabel: CK_UTF8CHAR_PTR, +) -> CK_RV { + log_with_thread_id!(error, "C_InitToken: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_InitPIN( + _hSession: CK_SESSION_HANDLE, + _pPin: CK_UTF8CHAR_PTR, + _ulPinLen: CK_ULONG, +) -> CK_RV { + log_with_thread_id!(error, "C_InitPIN: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_SetPIN( + _hSession: CK_SESSION_HANDLE, + _pOldPin: CK_UTF8CHAR_PTR, + _ulOldLen: CK_ULONG, + _pNewPin: CK_UTF8CHAR_PTR, + _ulNewLen: CK_ULONG, +) -> CK_RV { + log_with_thread_id!(error, "C_SetPIN: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +/// This gets called to create a new session. This module defers to the `ManagerProxy` to implement +/// this. +extern "C" fn C_OpenSession( + slotID: CK_SLOT_ID, + _flags: CK_FLAGS, + _pApplication: CK_VOID_PTR, + _Notify: CK_NOTIFY, + phSession: CK_SESSION_HANDLE_PTR, +) -> CK_RV { + if (slotID != SLOT_ID_MODERN && slotID != SLOT_ID_LEGACY) || phSession.is_null() { + log_with_thread_id!(error, "C_OpenSession: CKR_ARGUMENTS_BAD"); + return CKR_ARGUMENTS_BAD; + } + let mut manager_guard = try_to_get_manager_guard!(); + let manager = manager_guard_to_manager!(manager_guard); + let slot_type = if slotID == SLOT_ID_MODERN { + SlotType::Modern + } else { + SlotType::Legacy + }; + let session_handle = match manager.open_session(slot_type) { + Ok(session_handle) => session_handle, + Err(()) => { + log_with_thread_id!(error, "C_OpenSession: open_session failed"); + return CKR_DEVICE_ERROR; + } + }; + unsafe { + *phSession = session_handle; + } + log_with_thread_id!(debug, "C_OpenSession: CKR_OK"); + CKR_OK +} + +/// This gets called to close a session. This is handled by the `ManagerProxy`. +extern "C" fn C_CloseSession(hSession: CK_SESSION_HANDLE) -> CK_RV { + let mut manager_guard = try_to_get_manager_guard!(); + let manager = manager_guard_to_manager!(manager_guard); + if manager.close_session(hSession).is_err() { + log_with_thread_id!(error, "C_CloseSession: CKR_SESSION_HANDLE_INVALID"); + return CKR_SESSION_HANDLE_INVALID; + } + log_with_thread_id!(debug, "C_CloseSession: CKR_OK"); + CKR_OK +} + +/// This gets called to close all open sessions at once. This is handled by the `ManagerProxy`. +extern "C" fn C_CloseAllSessions(slotID: CK_SLOT_ID) -> CK_RV { + if slotID != SLOT_ID_MODERN && slotID != SLOT_ID_LEGACY { + log_with_thread_id!(error, "C_CloseAllSessions: CKR_ARGUMENTS_BAD"); + return CKR_ARGUMENTS_BAD; + } + let mut manager_guard = try_to_get_manager_guard!(); + let manager = manager_guard_to_manager!(manager_guard); + let slot_type = if slotID == SLOT_ID_MODERN { + SlotType::Modern + } else { + SlotType::Legacy + }; + match manager.close_all_sessions(slot_type) { + Ok(()) => { + log_with_thread_id!(debug, "C_CloseAllSessions: CKR_OK"); + CKR_OK + } + Err(()) => { + log_with_thread_id!(error, "C_CloseAllSessions: close_all_sessions failed"); + CKR_DEVICE_ERROR + } + } +} + +extern "C" fn C_GetSessionInfo(_hSession: CK_SESSION_HANDLE, _pInfo: CK_SESSION_INFO_PTR) -> CK_RV { + log_with_thread_id!(error, "C_GetSessionInfo: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_GetOperationState( + _hSession: CK_SESSION_HANDLE, + _pOperationState: CK_BYTE_PTR, + _pulOperationStateLen: CK_ULONG_PTR, +) -> CK_RV { + log_with_thread_id!(error, "C_GetOperationState: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_SetOperationState( + _hSession: CK_SESSION_HANDLE, + _pOperationState: CK_BYTE_PTR, + _ulOperationStateLen: CK_ULONG, + _hEncryptionKey: CK_OBJECT_HANDLE, + _hAuthenticationKey: CK_OBJECT_HANDLE, +) -> CK_RV { + log_with_thread_id!(error, "C_SetOperationState: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_Login( + _hSession: CK_SESSION_HANDLE, + _userType: CK_USER_TYPE, + _pPin: CK_UTF8CHAR_PTR, + _ulPinLen: CK_ULONG, +) -> CK_RV { + log_with_thread_id!(error, "C_Login: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +/// This gets called to log out and drop any authenticated resources. Because this module does not +/// hold on to authenticated resources, this module "implements" this by doing nothing and +/// returning a success result. +extern "C" fn C_Logout(_hSession: CK_SESSION_HANDLE) -> CK_RV { + log_with_thread_id!(debug, "C_Logout: CKR_OK"); + CKR_OK +} + +extern "C" fn C_CreateObject( + _hSession: CK_SESSION_HANDLE, + _pTemplate: CK_ATTRIBUTE_PTR, + _ulCount: CK_ULONG, + _phObject: CK_OBJECT_HANDLE_PTR, +) -> CK_RV { + log_with_thread_id!(error, "C_CreateObject: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_CopyObject( + _hSession: CK_SESSION_HANDLE, + _hObject: CK_OBJECT_HANDLE, + _pTemplate: CK_ATTRIBUTE_PTR, + _ulCount: CK_ULONG, + _phNewObject: CK_OBJECT_HANDLE_PTR, +) -> CK_RV { + log_with_thread_id!(error, "C_CopyObject: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_DestroyObject(_hSession: CK_SESSION_HANDLE, _hObject: CK_OBJECT_HANDLE) -> CK_RV { + log_with_thread_id!(error, "C_DestroyObject: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_GetObjectSize( + _hSession: CK_SESSION_HANDLE, + _hObject: CK_OBJECT_HANDLE, + _pulSize: CK_ULONG_PTR, +) -> CK_RV { + log_with_thread_id!(error, "C_GetObjectSize: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +/// This gets called to obtain the values of a number of attributes of an object identified by the +/// given handle. This module implements this by requesting that the `ManagerProxy` find the object +/// and attempt to get the value of each attribute. If a specified attribute is not defined on the +/// object, the length of that attribute is set to -1 to indicate that it is not available. +/// This gets called twice: once to obtain the lengths of the attributes and again to get the +/// values. +extern "C" fn C_GetAttributeValue( + _hSession: CK_SESSION_HANDLE, + hObject: CK_OBJECT_HANDLE, + pTemplate: CK_ATTRIBUTE_PTR, + ulCount: CK_ULONG, +) -> CK_RV { + if pTemplate.is_null() { + log_with_thread_id!(error, "C_GetAttributeValue: CKR_ARGUMENTS_BAD"); + return CKR_ARGUMENTS_BAD; + } + let mut attr_types = Vec::with_capacity(ulCount as usize); + for i in 0..ulCount { + let attr = unsafe { &*pTemplate.offset(i as isize) }; + attr_types.push(attr.attrType); + } + let mut manager_guard = try_to_get_manager_guard!(); + let manager = manager_guard_to_manager!(manager_guard); + let values = match manager.get_attributes(hObject, attr_types) { + Ok(values) => values, + Err(()) => { + log_with_thread_id!(error, "C_GetAttributeValue: CKR_ARGUMENTS_BAD"); + return CKR_ARGUMENTS_BAD; + } + }; + if values.len() != ulCount as usize { + log_with_thread_id!( + error, + "C_GetAttributeValue: manager.get_attributes didn't return the right number of values" + ); + return CKR_DEVICE_ERROR; + } + for i in 0..ulCount as usize { + let mut attr = unsafe { &mut *pTemplate.offset(i as isize) }; + // NB: the safety of this array access depends on the length check above + if let Some(attr_value) = &values[i] { + if attr.pValue.is_null() { + attr.ulValueLen = attr_value.len() as CK_ULONG; + } else { + let ptr: *mut u8 = attr.pValue as *mut u8; + if attr_value.len() != attr.ulValueLen as usize { + log_with_thread_id!(error, "C_GetAttributeValue: incorrect attr size"); + return CKR_ARGUMENTS_BAD; + } + unsafe { + std::ptr::copy_nonoverlapping(attr_value.as_ptr(), ptr, attr_value.len()); + } + } + } else { + attr.ulValueLen = (0 - 1) as CK_ULONG; + } + } + log_with_thread_id!(debug, "C_GetAttributeValue: CKR_OK"); + CKR_OK +} + +extern "C" fn C_SetAttributeValue( + _hSession: CK_SESSION_HANDLE, + _hObject: CK_OBJECT_HANDLE, + _pTemplate: CK_ATTRIBUTE_PTR, + _ulCount: CK_ULONG, +) -> CK_RV { + log_with_thread_id!(error, "C_SetAttributeValue: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +fn trace_attr(prefix: &str, attr: &CK_ATTRIBUTE) { + let typ = match unsafe_packed_field_access!(attr.attrType) { + CKA_CLASS => "CKA_CLASS".to_string(), + CKA_TOKEN => "CKA_TOKEN".to_string(), + CKA_LABEL => "CKA_LABEL".to_string(), + CKA_ID => "CKA_ID".to_string(), + CKA_VALUE => "CKA_VALUE".to_string(), + CKA_ISSUER => "CKA_ISSUER".to_string(), + CKA_SERIAL_NUMBER => "CKA_SERIAL_NUMBER".to_string(), + CKA_SUBJECT => "CKA_SUBJECT".to_string(), + CKA_PRIVATE => "CKA_PRIVATE".to_string(), + CKA_KEY_TYPE => "CKA_KEY_TYPE".to_string(), + CKA_MODULUS => "CKA_MODULUS".to_string(), + CKA_EC_PARAMS => "CKA_EC_PARAMS".to_string(), + _ => format!("0x{:x}", unsafe_packed_field_access!(attr.attrType)), + }; + let value = + unsafe { std::slice::from_raw_parts(attr.pValue as *const u8, attr.ulValueLen as usize) }; + log_with_thread_id!( + trace, + "{}CK_ATTRIBUTE {{ attrType: {}, pValue: {:?}, ulValueLen: {} }}", + prefix, + typ, + value, + unsafe_packed_field_access!(attr.ulValueLen) + ); +} + +/// This gets called to initialize a search for objects matching a given list of attributes. This +/// module implements this by gathering the attributes and passing them to the `ManagerProxy` to +/// start the search. +extern "C" fn C_FindObjectsInit( + hSession: CK_SESSION_HANDLE, + pTemplate: CK_ATTRIBUTE_PTR, + ulCount: CK_ULONG, +) -> CK_RV { + if pTemplate.is_null() { + log_with_thread_id!(error, "C_FindObjectsInit: CKR_ARGUMENTS_BAD"); + return CKR_ARGUMENTS_BAD; + } + let mut attrs = Vec::new(); + log_with_thread_id!(trace, "C_FindObjectsInit:"); + for i in 0..ulCount { + let attr = unsafe { &*pTemplate.offset(i as isize) }; + trace_attr(" ", &attr); + let slice = unsafe { + std::slice::from_raw_parts(attr.pValue as *const u8, attr.ulValueLen as usize) + }; + attrs.push((attr.attrType, slice.to_owned())); + } + let mut manager_guard = try_to_get_manager_guard!(); + let manager = manager_guard_to_manager!(manager_guard); + match manager.start_search(hSession, attrs) { + Ok(()) => {} + Err(()) => { + log_with_thread_id!(error, "C_FindObjectsInit: CKR_ARGUMENTS_BAD"); + return CKR_ARGUMENTS_BAD; + } + } + log_with_thread_id!(debug, "C_FindObjectsInit: CKR_OK"); + CKR_OK +} + +/// This gets called after `C_FindObjectsInit` to get the results of a search. This module +/// implements this by looking up the search in the `ManagerProxy` and copying out the matching +/// object handles. +extern "C" fn C_FindObjects( + hSession: CK_SESSION_HANDLE, + phObject: CK_OBJECT_HANDLE_PTR, + ulMaxObjectCount: CK_ULONG, + pulObjectCount: CK_ULONG_PTR, +) -> CK_RV { + if phObject.is_null() || pulObjectCount.is_null() || ulMaxObjectCount == 0 { + log_with_thread_id!(error, "C_FindObjects: CKR_ARGUMENTS_BAD"); + return CKR_ARGUMENTS_BAD; + } + let mut manager_guard = try_to_get_manager_guard!(); + let manager = manager_guard_to_manager!(manager_guard); + let handles = match manager.search(hSession, ulMaxObjectCount as usize) { + Ok(handles) => handles, + Err(()) => { + log_with_thread_id!(error, "C_FindObjects: CKR_ARGUMENTS_BAD"); + return CKR_ARGUMENTS_BAD; + } + }; + log_with_thread_id!(debug, "C_FindObjects: found handles {:?}", handles); + if handles.len() > ulMaxObjectCount as usize { + log_with_thread_id!(error, "C_FindObjects: manager returned too many handles"); + return CKR_DEVICE_ERROR; + } + unsafe { + *pulObjectCount = handles.len() as CK_ULONG; + } + for (index, handle) in handles.iter().enumerate() { + if index < ulMaxObjectCount as usize { + unsafe { + *(phObject.add(index)) = *handle; + } + } + } + log_with_thread_id!(debug, "C_FindObjects: CKR_OK"); + CKR_OK +} + +/// This gets called after `C_FindObjectsInit` and `C_FindObjects` to finish a search. The module +/// tells the `ManagerProxy` to clear the search. +extern "C" fn C_FindObjectsFinal(hSession: CK_SESSION_HANDLE) -> CK_RV { + let mut manager_guard = try_to_get_manager_guard!(); + let manager = manager_guard_to_manager!(manager_guard); + // It would be an error if there were no search for this session, but we can be permissive here. + match manager.clear_search(hSession) { + Ok(()) => { + log_with_thread_id!(debug, "C_FindObjectsFinal: CKR_OK"); + CKR_OK + } + Err(()) => { + log_with_thread_id!(error, "C_FindObjectsFinal: clear_search failed"); + CKR_DEVICE_ERROR + } + } +} + +extern "C" fn C_EncryptInit( + _hSession: CK_SESSION_HANDLE, + _pMechanism: CK_MECHANISM_PTR, + _hKey: CK_OBJECT_HANDLE, +) -> CK_RV { + log_with_thread_id!(error, "C_EncryptInit: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_Encrypt( + _hSession: CK_SESSION_HANDLE, + _pData: CK_BYTE_PTR, + _ulDataLen: CK_ULONG, + _pEncryptedData: CK_BYTE_PTR, + _pulEncryptedDataLen: CK_ULONG_PTR, +) -> CK_RV { + log_with_thread_id!(error, "C_Encrypt: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_EncryptUpdate( + _hSession: CK_SESSION_HANDLE, + _pPart: CK_BYTE_PTR, + _ulPartLen: CK_ULONG, + _pEncryptedPart: CK_BYTE_PTR, + _pulEncryptedPartLen: CK_ULONG_PTR, +) -> CK_RV { + log_with_thread_id!(error, "C_EncryptUpdate: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_EncryptFinal( + _hSession: CK_SESSION_HANDLE, + _pLastEncryptedPart: CK_BYTE_PTR, + _pulLastEncryptedPartLen: CK_ULONG_PTR, +) -> CK_RV { + log_with_thread_id!(error, "C_EncryptFinal: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_DecryptInit( + _hSession: CK_SESSION_HANDLE, + _pMechanism: CK_MECHANISM_PTR, + _hKey: CK_OBJECT_HANDLE, +) -> CK_RV { + log_with_thread_id!(error, "C_DecryptInit: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_Decrypt( + _hSession: CK_SESSION_HANDLE, + _pEncryptedData: CK_BYTE_PTR, + _ulEncryptedDataLen: CK_ULONG, + _pData: CK_BYTE_PTR, + _pulDataLen: CK_ULONG_PTR, +) -> CK_RV { + log_with_thread_id!(error, "C_Decrypt: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_DecryptUpdate( + _hSession: CK_SESSION_HANDLE, + _pEncryptedPart: CK_BYTE_PTR, + _ulEncryptedPartLen: CK_ULONG, + _pPart: CK_BYTE_PTR, + _pulPartLen: CK_ULONG_PTR, +) -> CK_RV { + log_with_thread_id!(error, "C_DecryptUpdate: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_DecryptFinal( + _hSession: CK_SESSION_HANDLE, + _pLastPart: CK_BYTE_PTR, + _pulLastPartLen: CK_ULONG_PTR, +) -> CK_RV { + log_with_thread_id!(error, "C_DecryptFinal: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_DigestInit(_hSession: CK_SESSION_HANDLE, _pMechanism: CK_MECHANISM_PTR) -> CK_RV { + log_with_thread_id!(error, "C_DigestInit: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_Digest( + _hSession: CK_SESSION_HANDLE, + _pData: CK_BYTE_PTR, + _ulDataLen: CK_ULONG, + _pDigest: CK_BYTE_PTR, + _pulDigestLen: CK_ULONG_PTR, +) -> CK_RV { + log_with_thread_id!(error, "C_Digest: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_DigestUpdate( + _hSession: CK_SESSION_HANDLE, + _pPart: CK_BYTE_PTR, + _ulPartLen: CK_ULONG, +) -> CK_RV { + log_with_thread_id!(error, "C_DigestUpdate: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_DigestKey(_hSession: CK_SESSION_HANDLE, _hKey: CK_OBJECT_HANDLE) -> CK_RV { + log_with_thread_id!(error, "C_DigestKey: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_DigestFinal( + _hSession: CK_SESSION_HANDLE, + _pDigest: CK_BYTE_PTR, + _pulDigestLen: CK_ULONG_PTR, +) -> CK_RV { + log_with_thread_id!(error, "C_DigestFinal: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +/// This gets called to set up a sign operation. The module essentially defers to the +/// `ManagerProxy`. +extern "C" fn C_SignInit( + hSession: CK_SESSION_HANDLE, + pMechanism: CK_MECHANISM_PTR, + hKey: CK_OBJECT_HANDLE, +) -> CK_RV { + if pMechanism.is_null() { + log_with_thread_id!(error, "C_SignInit: CKR_ARGUMENTS_BAD"); + return CKR_ARGUMENTS_BAD; + } + // Presumably we should validate the mechanism against hKey, but the specification doesn't + // actually seem to require this. + let mechanism = unsafe { *pMechanism }; + log_with_thread_id!(debug, "C_SignInit: mechanism is {:?}", mechanism); + let mechanism_params = if mechanism.mechanism == CKM_RSA_PKCS_PSS { + if mechanism.ulParameterLen as usize != std::mem::size_of::() { + log_with_thread_id!( + error, + "C_SignInit: bad ulParameterLen for CKM_RSA_PKCS_PSS: {}", + unsafe_packed_field_access!(mechanism.ulParameterLen) + ); + return CKR_ARGUMENTS_BAD; + } + Some(unsafe { *(mechanism.pParameter as *const CK_RSA_PKCS_PSS_PARAMS) }) + } else { + None + }; + let mut manager_guard = try_to_get_manager_guard!(); + let manager = manager_guard_to_manager!(manager_guard); + match manager.start_sign(hSession, hKey, mechanism_params) { + Ok(()) => {} + Err(()) => { + log_with_thread_id!(error, "C_SignInit: CKR_GENERAL_ERROR"); + return CKR_GENERAL_ERROR; + } + }; + log_with_thread_id!(debug, "C_SignInit: CKR_OK"); + CKR_OK +} + +/// NSS calls this after `C_SignInit` (there are more ways in the PKCS #11 specification to sign +/// data, but this is the only way supported by this module). The module essentially defers to the +/// `ManagerProxy` and copies out the resulting signature. +extern "C" fn C_Sign( + hSession: CK_SESSION_HANDLE, + pData: CK_BYTE_PTR, + ulDataLen: CK_ULONG, + pSignature: CK_BYTE_PTR, + pulSignatureLen: CK_ULONG_PTR, +) -> CK_RV { + if pData.is_null() || pulSignatureLen.is_null() { + log_with_thread_id!(error, "C_Sign: CKR_ARGUMENTS_BAD"); + return CKR_ARGUMENTS_BAD; + } + let data = unsafe { std::slice::from_raw_parts(pData, ulDataLen as usize) }; + if pSignature.is_null() { + let mut manager_guard = try_to_get_manager_guard!(); + let manager = manager_guard_to_manager!(manager_guard); + match manager.get_signature_length(hSession, data.to_vec()) { + Ok(signature_length) => unsafe { + *pulSignatureLen = signature_length as CK_ULONG; + }, + Err(()) => { + log_with_thread_id!(error, "C_Sign: get_signature_length failed"); + return CKR_GENERAL_ERROR; + } + } + } else { + let mut manager_guard = try_to_get_manager_guard!(); + let manager = manager_guard_to_manager!(manager_guard); + match manager.sign(hSession, data.to_vec()) { + Ok(signature) => { + let signature_capacity = unsafe { *pulSignatureLen } as usize; + if signature_capacity < signature.len() { + log_with_thread_id!(error, "C_Sign: CKR_ARGUMENTS_BAD"); + return CKR_ARGUMENTS_BAD; + } + let ptr: *mut u8 = pSignature as *mut u8; + unsafe { + std::ptr::copy_nonoverlapping(signature.as_ptr(), ptr, signature.len()); + *pulSignatureLen = signature.len() as CK_ULONG; + } + } + Err(()) => { + log_with_thread_id!(error, "C_Sign: sign failed"); + return CKR_GENERAL_ERROR; + } + } + } + log_with_thread_id!(debug, "C_Sign: CKR_OK"); + CKR_OK +} + +extern "C" fn C_SignUpdate( + _hSession: CK_SESSION_HANDLE, + _pPart: CK_BYTE_PTR, + _ulPartLen: CK_ULONG, +) -> CK_RV { + log_with_thread_id!(error, "C_SignUpdate: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_SignFinal( + _hSession: CK_SESSION_HANDLE, + _pSignature: CK_BYTE_PTR, + _pulSignatureLen: CK_ULONG_PTR, +) -> CK_RV { + log_with_thread_id!(error, "C_SignFinal: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_SignRecoverInit( + _hSession: CK_SESSION_HANDLE, + _pMechanism: CK_MECHANISM_PTR, + _hKey: CK_OBJECT_HANDLE, +) -> CK_RV { + log_with_thread_id!(error, "C_SignRecoverInit: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_SignRecover( + _hSession: CK_SESSION_HANDLE, + _pData: CK_BYTE_PTR, + _ulDataLen: CK_ULONG, + _pSignature: CK_BYTE_PTR, + _pulSignatureLen: CK_ULONG_PTR, +) -> CK_RV { + log_with_thread_id!(error, "C_SignRecover: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_VerifyInit( + _hSession: CK_SESSION_HANDLE, + _pMechanism: CK_MECHANISM_PTR, + _hKey: CK_OBJECT_HANDLE, +) -> CK_RV { + log_with_thread_id!(error, "C_VerifyInit: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_Verify( + _hSession: CK_SESSION_HANDLE, + _pData: CK_BYTE_PTR, + _ulDataLen: CK_ULONG, + _pSignature: CK_BYTE_PTR, + _ulSignatureLen: CK_ULONG, +) -> CK_RV { + log_with_thread_id!(error, "C_Verify: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_VerifyUpdate( + _hSession: CK_SESSION_HANDLE, + _pPart: CK_BYTE_PTR, + _ulPartLen: CK_ULONG, +) -> CK_RV { + log_with_thread_id!(error, "C_VerifyUpdate: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_VerifyFinal( + _hSession: CK_SESSION_HANDLE, + _pSignature: CK_BYTE_PTR, + _ulSignatureLen: CK_ULONG, +) -> CK_RV { + log_with_thread_id!(error, "C_VerifyFinal: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_VerifyRecoverInit( + _hSession: CK_SESSION_HANDLE, + _pMechanism: CK_MECHANISM_PTR, + _hKey: CK_OBJECT_HANDLE, +) -> CK_RV { + log_with_thread_id!(error, "C_VerifyRecoverInit: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_VerifyRecover( + _hSession: CK_SESSION_HANDLE, + _pSignature: CK_BYTE_PTR, + _ulSignatureLen: CK_ULONG, + _pData: CK_BYTE_PTR, + _pulDataLen: CK_ULONG_PTR, +) -> CK_RV { + log_with_thread_id!(error, "C_VerifyRecover: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_DigestEncryptUpdate( + _hSession: CK_SESSION_HANDLE, + _pPart: CK_BYTE_PTR, + _ulPartLen: CK_ULONG, + _pEncryptedPart: CK_BYTE_PTR, + _pulEncryptedPartLen: CK_ULONG_PTR, +) -> CK_RV { + log_with_thread_id!(error, "C_DigestEncryptUpdate: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_DecryptDigestUpdate( + _hSession: CK_SESSION_HANDLE, + _pEncryptedPart: CK_BYTE_PTR, + _ulEncryptedPartLen: CK_ULONG, + _pPart: CK_BYTE_PTR, + _pulPartLen: CK_ULONG_PTR, +) -> CK_RV { + log_with_thread_id!(error, "C_DecryptDigestUpdate: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_SignEncryptUpdate( + _hSession: CK_SESSION_HANDLE, + _pPart: CK_BYTE_PTR, + _ulPartLen: CK_ULONG, + _pEncryptedPart: CK_BYTE_PTR, + _pulEncryptedPartLen: CK_ULONG_PTR, +) -> CK_RV { + log_with_thread_id!(error, "C_SignEncryptUpdate: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_DecryptVerifyUpdate( + _hSession: CK_SESSION_HANDLE, + _pEncryptedPart: CK_BYTE_PTR, + _ulEncryptedPartLen: CK_ULONG, + _pPart: CK_BYTE_PTR, + _pulPartLen: CK_ULONG_PTR, +) -> CK_RV { + log_with_thread_id!(error, "C_DecryptVerifyUpdate: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_GenerateKey( + _hSession: CK_SESSION_HANDLE, + _pMechanism: CK_MECHANISM_PTR, + _pTemplate: CK_ATTRIBUTE_PTR, + _ulCount: CK_ULONG, + _phKey: CK_OBJECT_HANDLE_PTR, +) -> CK_RV { + log_with_thread_id!(error, "C_GenerateKey: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_GenerateKeyPair( + _hSession: CK_SESSION_HANDLE, + _pMechanism: CK_MECHANISM_PTR, + _pPublicKeyTemplate: CK_ATTRIBUTE_PTR, + _ulPublicKeyAttributeCount: CK_ULONG, + _pPrivateKeyTemplate: CK_ATTRIBUTE_PTR, + _ulPrivateKeyAttributeCount: CK_ULONG, + _phPublicKey: CK_OBJECT_HANDLE_PTR, + _phPrivateKey: CK_OBJECT_HANDLE_PTR, +) -> CK_RV { + log_with_thread_id!(error, "C_GenerateKeyPair: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_WrapKey( + _hSession: CK_SESSION_HANDLE, + _pMechanism: CK_MECHANISM_PTR, + _hWrappingKey: CK_OBJECT_HANDLE, + _hKey: CK_OBJECT_HANDLE, + _pWrappedKey: CK_BYTE_PTR, + _pulWrappedKeyLen: CK_ULONG_PTR, +) -> CK_RV { + log_with_thread_id!(error, "C_WrapKey: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_UnwrapKey( + _hSession: CK_SESSION_HANDLE, + _pMechanism: CK_MECHANISM_PTR, + _hUnwrappingKey: CK_OBJECT_HANDLE, + _pWrappedKey: CK_BYTE_PTR, + _ulWrappedKeyLen: CK_ULONG, + _pTemplate: CK_ATTRIBUTE_PTR, + _ulAttributeCount: CK_ULONG, + _phKey: CK_OBJECT_HANDLE_PTR, +) -> CK_RV { + log_with_thread_id!(error, "C_UnwrapKey: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_DeriveKey( + _hSession: CK_SESSION_HANDLE, + _pMechanism: CK_MECHANISM_PTR, + _hBaseKey: CK_OBJECT_HANDLE, + _pTemplate: CK_ATTRIBUTE_PTR, + _ulAttributeCount: CK_ULONG, + _phKey: CK_OBJECT_HANDLE_PTR, +) -> CK_RV { + log_with_thread_id!(error, "C_DeriveKey: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_SeedRandom( + _hSession: CK_SESSION_HANDLE, + _pSeed: CK_BYTE_PTR, + _ulSeedLen: CK_ULONG, +) -> CK_RV { + log_with_thread_id!(error, "C_SeedRandom: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_GenerateRandom( + _hSession: CK_SESSION_HANDLE, + _RandomData: CK_BYTE_PTR, + _ulRandomLen: CK_ULONG, +) -> CK_RV { + log_with_thread_id!(error, "C_GenerateRandom: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_GetFunctionStatus(_hSession: CK_SESSION_HANDLE) -> CK_RV { + log_with_thread_id!(error, "C_GetFunctionStatus: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_CancelFunction(_hSession: CK_SESSION_HANDLE) -> CK_RV { + log_with_thread_id!(error, "C_CancelFunction: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +extern "C" fn C_WaitForSlotEvent( + _flags: CK_FLAGS, + _pSlot: CK_SLOT_ID_PTR, + _pRserved: CK_VOID_PTR, +) -> CK_RV { + log_with_thread_id!(error, "C_WaitForSlotEvent: CKR_FUNCTION_NOT_SUPPORTED"); + CKR_FUNCTION_NOT_SUPPORTED +} + +/// To be a valid PKCS #11 module, this list of functions must be supported. At least cryptoki 2.2 +/// must be supported for this module to work in NSS. +static mut FUNCTION_LIST: CK_FUNCTION_LIST = CK_FUNCTION_LIST { + version: CK_VERSION { major: 2, minor: 2 }, + C_Initialize: Some(C_Initialize), + C_Finalize: Some(C_Finalize), + C_GetInfo: Some(C_GetInfo), + C_GetFunctionList: None, + C_GetSlotList: Some(C_GetSlotList), + C_GetSlotInfo: Some(C_GetSlotInfo), + C_GetTokenInfo: Some(C_GetTokenInfo), + C_GetMechanismList: Some(C_GetMechanismList), + C_GetMechanismInfo: Some(C_GetMechanismInfo), + C_InitToken: Some(C_InitToken), + C_InitPIN: Some(C_InitPIN), + C_SetPIN: Some(C_SetPIN), + C_OpenSession: Some(C_OpenSession), + C_CloseSession: Some(C_CloseSession), + C_CloseAllSessions: Some(C_CloseAllSessions), + C_GetSessionInfo: Some(C_GetSessionInfo), + C_GetOperationState: Some(C_GetOperationState), + C_SetOperationState: Some(C_SetOperationState), + C_Login: Some(C_Login), + C_Logout: Some(C_Logout), + C_CreateObject: Some(C_CreateObject), + C_CopyObject: Some(C_CopyObject), + C_DestroyObject: Some(C_DestroyObject), + C_GetObjectSize: Some(C_GetObjectSize), + C_GetAttributeValue: Some(C_GetAttributeValue), + C_SetAttributeValue: Some(C_SetAttributeValue), + C_FindObjectsInit: Some(C_FindObjectsInit), + C_FindObjects: Some(C_FindObjects), + C_FindObjectsFinal: Some(C_FindObjectsFinal), + C_EncryptInit: Some(C_EncryptInit), + C_Encrypt: Some(C_Encrypt), + C_EncryptUpdate: Some(C_EncryptUpdate), + C_EncryptFinal: Some(C_EncryptFinal), + C_DecryptInit: Some(C_DecryptInit), + C_Decrypt: Some(C_Decrypt), + C_DecryptUpdate: Some(C_DecryptUpdate), + C_DecryptFinal: Some(C_DecryptFinal), + C_DigestInit: Some(C_DigestInit), + C_Digest: Some(C_Digest), + C_DigestUpdate: Some(C_DigestUpdate), + C_DigestKey: Some(C_DigestKey), + C_DigestFinal: Some(C_DigestFinal), + C_SignInit: Some(C_SignInit), + C_Sign: Some(C_Sign), + C_SignUpdate: Some(C_SignUpdate), + C_SignFinal: Some(C_SignFinal), + C_SignRecoverInit: Some(C_SignRecoverInit), + C_SignRecover: Some(C_SignRecover), + C_VerifyInit: Some(C_VerifyInit), + C_Verify: Some(C_Verify), + C_VerifyUpdate: Some(C_VerifyUpdate), + C_VerifyFinal: Some(C_VerifyFinal), + C_VerifyRecoverInit: Some(C_VerifyRecoverInit), + C_VerifyRecover: Some(C_VerifyRecover), + C_DigestEncryptUpdate: Some(C_DigestEncryptUpdate), + C_DecryptDigestUpdate: Some(C_DecryptDigestUpdate), + C_SignEncryptUpdate: Some(C_SignEncryptUpdate), + C_DecryptVerifyUpdate: Some(C_DecryptVerifyUpdate), + C_GenerateKey: Some(C_GenerateKey), + C_GenerateKeyPair: Some(C_GenerateKeyPair), + C_WrapKey: Some(C_WrapKey), + C_UnwrapKey: Some(C_UnwrapKey), + C_DeriveKey: Some(C_DeriveKey), + C_SeedRandom: Some(C_SeedRandom), + C_GenerateRandom: Some(C_GenerateRandom), + C_GetFunctionStatus: Some(C_GetFunctionStatus), + C_CancelFunction: Some(C_CancelFunction), + C_WaitForSlotEvent: Some(C_WaitForSlotEvent), +}; + +/// This is the only function this module exposes. NSS calls it to obtain the list of functions +/// comprising this module. +#[no_mangle] +pub extern "C" fn C_GetFunctionList(ppFunctionList: CK_FUNCTION_LIST_PTR_PTR) -> CK_RV { + if ppFunctionList.is_null() { + return CKR_ARGUMENTS_BAD; + } + unsafe { + *ppFunctionList = &mut FUNCTION_LIST; + } + CKR_OK +} + +#[cfg_attr(target_os = "macos", link(name = "Security", kind = "framework"))] +extern "C" {} diff --git a/security/manager/ssl/osclientcerts/src/manager.rs b/security/manager/ssl/osclientcerts/src/manager.rs new file mode 100644 index 0000000000..de4be1272a --- /dev/null +++ b/security/manager/ssl/osclientcerts/src/manager.rs @@ -0,0 +1,614 @@ +/* -*- Mode: rust; rust-indent-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use pkcs11::types::*; +use std::collections::{BTreeMap, BTreeSet}; + +#[cfg(target_os = "macos")] +use crate::backend_macos as backend; +#[cfg(target_os = "windows")] +use crate::backend_windows as backend; +use crate::util::*; +use backend::*; + +use std::sync::mpsc::{channel, Receiver, Sender}; +use std::thread; +use std::thread::JoinHandle; +use std::time::{Duration, Instant}; + +/// Helper enum to differentiate between sessions on the modern slot and sessions on the legacy +/// slot. The former is for EC keys and RSA keys that can be used with RSA-PSS whereas the latter is +/// for RSA keys that cannot be used with RSA-PSS. +#[derive(Clone, Copy, PartialEq)] +pub enum SlotType { + Modern, + Legacy, +} + +/// Helper type for sending `ManagerArguments` to the real `Manager`. +type ManagerArgumentsSender = Sender; +/// Helper type for receiving `ManagerReturnValue`s from the real `Manager`. +type ManagerReturnValueReceiver = Receiver; + +/// Helper enum that encapsulates arguments to send from the `ManagerProxy` to the real `Manager`. +/// `ManagerArguments::Stop` is a special variant that stops the background thread and drops the +/// `Manager`. +enum ManagerArguments { + OpenSession(SlotType), + CloseSession(CK_SESSION_HANDLE), + CloseAllSessions(SlotType), + StartSearch(CK_SESSION_HANDLE, Vec<(CK_ATTRIBUTE_TYPE, Vec)>), + Search(CK_SESSION_HANDLE, usize), + ClearSearch(CK_SESSION_HANDLE), + GetAttributes(CK_OBJECT_HANDLE, Vec), + StartSign( + CK_SESSION_HANDLE, + CK_OBJECT_HANDLE, + Option, + ), + GetSignatureLength(CK_SESSION_HANDLE, Vec), + Sign(CK_SESSION_HANDLE, Vec), + Stop, +} + +/// Helper enum that encapsulates return values from the real `Manager` that are sent back to the +/// `ManagerProxy`. `ManagerReturnValue::Stop` is a special variant that indicates that the +/// `Manager` will stop. +enum ManagerReturnValue { + OpenSession(Result), + CloseSession(Result<(), ()>), + CloseAllSessions(Result<(), ()>), + StartSearch(Result<(), ()>), + Search(Result, ()>), + ClearSearch(Result<(), ()>), + GetAttributes(Result>>, ()>), + StartSign(Result<(), ()>), + GetSignatureLength(Result), + Sign(Result, ()>), + Stop(Result<(), ()>), +} + +/// Helper macro to implement the body of each public `ManagerProxy` function. Takes a +/// `ManagerProxy` instance (should always be `self`), a `ManagerArguments` representing the +/// `Manager` function to call and the arguments to use, and the qualified type of the expected +/// `ManagerReturnValue` that will be received from the `Manager` when it is done. +macro_rules! manager_proxy_fn_impl { + ($manager:ident, $argument_enum:expr, $return_type:path) => { + match $manager.proxy_call($argument_enum) { + Ok($return_type(result)) => result, + Ok(_) => { + error!("unexpected return value from manager"); + Err(()) + } + Err(()) => Err(()), + } + }; +} + +/// `ManagerProxy` synchronously proxies calls from any thread to the `Manager` that runs on a +/// single thread. This is necessary because the underlying OS APIs in use are not guaranteed to be +/// thread-safe (e.g. they may use thread-local storage). Using it should be identical to using the +/// real `Manager`. +pub struct ManagerProxy { + sender: ManagerArgumentsSender, + receiver: ManagerReturnValueReceiver, + thread_handle: Option>, +} + +impl ManagerProxy { + pub fn new() -> ManagerProxy { + let (proxy_sender, manager_receiver) = channel(); + let (manager_sender, proxy_receiver) = channel(); + let thread_handle = thread::spawn(move || { + let mut real_manager = Manager::new(); + loop { + let arguments = match manager_receiver.recv() { + Ok(arguments) => arguments, + Err(e) => { + error!("error recv()ing arguments from ManagerProxy: {}", e); + break; + } + }; + let results = match arguments { + ManagerArguments::OpenSession(slot_type) => { + ManagerReturnValue::OpenSession(real_manager.open_session(slot_type)) + } + ManagerArguments::CloseSession(session_handle) => { + ManagerReturnValue::CloseSession(real_manager.close_session(session_handle)) + } + ManagerArguments::CloseAllSessions(slot_type) => { + ManagerReturnValue::CloseAllSessions( + real_manager.close_all_sessions(slot_type), + ) + } + ManagerArguments::StartSearch(session, attrs) => { + ManagerReturnValue::StartSearch(real_manager.start_search(session, &attrs)) + } + ManagerArguments::Search(session, max_objects) => { + ManagerReturnValue::Search(real_manager.search(session, max_objects)) + } + ManagerArguments::ClearSearch(session) => { + ManagerReturnValue::ClearSearch(real_manager.clear_search(session)) + } + ManagerArguments::GetAttributes(object_handle, attr_types) => { + ManagerReturnValue::GetAttributes( + real_manager.get_attributes(object_handle, attr_types), + ) + } + ManagerArguments::StartSign(session, key_handle, params) => { + ManagerReturnValue::StartSign( + real_manager.start_sign(session, key_handle, params), + ) + } + ManagerArguments::GetSignatureLength(session, data) => { + ManagerReturnValue::GetSignatureLength( + real_manager.get_signature_length(session, &data), + ) + } + ManagerArguments::Sign(session, data) => { + ManagerReturnValue::Sign(real_manager.sign(session, &data)) + } + ManagerArguments::Stop => { + debug!("ManagerArguments::Stop received - stopping Manager thread."); + ManagerReturnValue::Stop(Ok(())) + } + }; + let stop_after_send = match &results { + &ManagerReturnValue::Stop(_) => true, + _ => false, + }; + match manager_sender.send(results) { + Ok(()) => {} + Err(e) => { + error!("error send()ing results from Manager: {}", e); + break; + } + } + if stop_after_send { + break; + } + } + }); + ManagerProxy { + sender: proxy_sender, + receiver: proxy_receiver, + thread_handle: Some(thread_handle), + } + } + + fn proxy_call(&self, args: ManagerArguments) -> Result { + match self.sender.send(args) { + Ok(()) => {} + Err(e) => { + error!("error send()ing arguments to Manager: {}", e); + return Err(()); + } + }; + let result = match self.receiver.recv() { + Ok(result) => result, + Err(e) => { + error!("error recv()ing result from Manager: {}", e); + return Err(()); + } + }; + Ok(result) + } + + pub fn open_session(&mut self, slot_type: SlotType) -> Result { + manager_proxy_fn_impl!( + self, + ManagerArguments::OpenSession(slot_type), + ManagerReturnValue::OpenSession + ) + } + + pub fn close_session(&mut self, session: CK_SESSION_HANDLE) -> Result<(), ()> { + manager_proxy_fn_impl!( + self, + ManagerArguments::CloseSession(session), + ManagerReturnValue::CloseSession + ) + } + + pub fn close_all_sessions(&mut self, slot_type: SlotType) -> Result<(), ()> { + manager_proxy_fn_impl!( + self, + ManagerArguments::CloseAllSessions(slot_type), + ManagerReturnValue::CloseAllSessions + ) + } + + pub fn start_search( + &mut self, + session: CK_SESSION_HANDLE, + attrs: Vec<(CK_ATTRIBUTE_TYPE, Vec)>, + ) -> Result<(), ()> { + manager_proxy_fn_impl!( + self, + ManagerArguments::StartSearch(session, attrs), + ManagerReturnValue::StartSearch + ) + } + + pub fn search( + &mut self, + session: CK_SESSION_HANDLE, + max_objects: usize, + ) -> Result, ()> { + manager_proxy_fn_impl!( + self, + ManagerArguments::Search(session, max_objects), + ManagerReturnValue::Search + ) + } + + pub fn clear_search(&mut self, session: CK_SESSION_HANDLE) -> Result<(), ()> { + manager_proxy_fn_impl!( + self, + ManagerArguments::ClearSearch(session), + ManagerReturnValue::ClearSearch + ) + } + + pub fn get_attributes( + &self, + object_handle: CK_OBJECT_HANDLE, + attr_types: Vec, + ) -> Result>>, ()> { + manager_proxy_fn_impl!( + self, + ManagerArguments::GetAttributes(object_handle, attr_types,), + ManagerReturnValue::GetAttributes + ) + } + + pub fn start_sign( + &mut self, + session: CK_SESSION_HANDLE, + key_handle: CK_OBJECT_HANDLE, + params: Option, + ) -> Result<(), ()> { + manager_proxy_fn_impl!( + self, + ManagerArguments::StartSign(session, key_handle, params), + ManagerReturnValue::StartSign + ) + } + + pub fn get_signature_length( + &self, + session: CK_SESSION_HANDLE, + data: Vec, + ) -> Result { + manager_proxy_fn_impl!( + self, + ManagerArguments::GetSignatureLength(session, data), + ManagerReturnValue::GetSignatureLength + ) + } + + pub fn sign(&mut self, session: CK_SESSION_HANDLE, data: Vec) -> Result, ()> { + manager_proxy_fn_impl!( + self, + ManagerArguments::Sign(session, data), + ManagerReturnValue::Sign + ) + } + + pub fn stop(&mut self) -> Result<(), ()> { + manager_proxy_fn_impl!(self, ManagerArguments::Stop, ManagerReturnValue::Stop)?; + let thread_handle = match self.thread_handle.take() { + Some(thread_handle) => thread_handle, + None => { + error!("stop should only be called once"); + return Err(()); + } + }; + match thread_handle.join() { + Ok(()) => {} + Err(e) => { + error!("manager thread panicked: {:?}", e); + return Err(()); + } + }; + Ok(()) + } +} + +// Determines if the attributes of a given search correspond to NSS looking for all certificates or +// private keys. Returns true if so, and false otherwise. +// These searches are of the form: +// { { type: CKA_TOKEN, value: [1] }, +// { type: CKA_CLASS, value: [CKO_CERTIFICATE or CKO_PRIVATE_KEY, as serialized bytes] } } +// (although not necessarily in that order - see nssToken_TraverseCertificates and +// nssToken_FindPrivateKeys) +fn search_is_for_all_certificates_or_keys( + attrs: &[(CK_ATTRIBUTE_TYPE, Vec)], +) -> Result { + if attrs.len() != 2 { + return Ok(false); + } + let token_bytes = vec![1 as u8]; + let mut found_token = false; + let cko_certificate_bytes = serialize_uint(CKO_CERTIFICATE)?; + let cko_private_key_bytes = serialize_uint(CKO_PRIVATE_KEY)?; + let mut found_certificate_or_private_key = false; + for (attr_type, attr_value) in attrs.iter() { + if attr_type == &CKA_TOKEN && attr_value == &token_bytes { + found_token = true; + } + if attr_type == &CKA_CLASS + && (attr_value == &cko_certificate_bytes || attr_value == &cko_private_key_bytes) + { + found_certificate_or_private_key = true; + } + } + Ok(found_token && found_certificate_or_private_key) +} + +/// The `Manager` keeps track of the state of this module with respect to the PKCS #11 +/// specification. This includes what sessions are open, which search and sign operations are +/// ongoing, and what objects are known and by what handle. +struct Manager { + /// A map of session to session type (modern or legacy). Sessions can be created (opened) and + /// later closed. + sessions: BTreeMap, + /// A map of searches to PKCS #11 object handles that match those searches. + searches: BTreeMap>, + /// A map of sign operations to a pair of the object handle and optionally some params being + /// used by each one. + signs: BTreeMap)>, + /// A map of object handles to the underlying objects. + objects: BTreeMap, + /// A set of certificate identifiers (not the same as handles). + cert_ids: BTreeSet>, + /// A set of key identifiers (not the same as handles). For each id in this set, there should be + /// a corresponding identical id in the `cert_ids` set, and vice-versa. + key_ids: BTreeSet>, + /// The next session handle to hand out. + next_session: CK_SESSION_HANDLE, + /// The next object handle to hand out. + next_handle: CK_OBJECT_HANDLE, + /// The last time the implementation looked for new objects in the backend. + /// The implementation does this search no more than once every 3 seconds. + last_scan_time: Option, +} + +impl Manager { + pub fn new() -> Manager { + let mut manager = Manager { + sessions: BTreeMap::new(), + searches: BTreeMap::new(), + signs: BTreeMap::new(), + objects: BTreeMap::new(), + cert_ids: BTreeSet::new(), + key_ids: BTreeSet::new(), + next_session: 1, + next_handle: 1, + last_scan_time: None, + }; + manager.maybe_find_new_objects(); + manager + } + + /// When a new `Manager` is created and when a new session is opened (provided at least 3 + /// seconds have elapsed since the last session was opened), this searches for certificates and + /// keys to expose. We de-duplicate previously-found certificates and keys by / keeping track of + /// their IDs. + fn maybe_find_new_objects(&mut self) { + let now = Instant::now(); + match self.last_scan_time { + Some(last_scan_time) => { + if now.duration_since(last_scan_time) < Duration::new(3, 0) { + return; + } + } + None => {} + } + self.last_scan_time = Some(now); + let objects = list_objects(); + debug!("found {} objects", objects.len()); + for object in objects { + match &object { + Object::Cert(cert) => { + if self.cert_ids.contains(cert.id()) { + continue; + } + self.cert_ids.insert(cert.id().to_vec()); + let handle = self.get_next_handle(); + self.objects.insert(handle, object); + } + Object::Key(key) => { + if self.key_ids.contains(key.id()) { + continue; + } + self.key_ids.insert(key.id().to_vec()); + let handle = self.get_next_handle(); + self.objects.insert(handle, object); + } + } + } + } + + pub fn open_session(&mut self, slot_type: SlotType) -> Result { + let next_session = self.next_session; + self.next_session += 1; + self.sessions.insert(next_session, slot_type); + Ok(next_session) + } + + pub fn close_session(&mut self, session: CK_SESSION_HANDLE) -> Result<(), ()> { + self.sessions.remove(&session).ok_or(()).map(|_| ()) + } + + pub fn close_all_sessions(&mut self, slot_type: SlotType) -> Result<(), ()> { + let mut to_remove = Vec::new(); + for (session, open_slot_type) in self.sessions.iter() { + if slot_type == *open_slot_type { + to_remove.push(*session); + } + } + for session in to_remove { + if self.sessions.remove(&session).is_none() { + return Err(()); + } + } + Ok(()) + } + + fn get_next_handle(&mut self) -> CK_OBJECT_HANDLE { + let next_handle = self.next_handle; + self.next_handle += 1; + next_handle + } + + /// PKCS #11 specifies that search operations happen in three phases: setup, get any matches + /// (this part may be repeated if the caller uses a small buffer), and end. This implementation + /// does all of the work up front and gathers all matching objects during setup and retains them + /// until they are retrieved and consumed via `search`. + pub fn start_search( + &mut self, + session: CK_SESSION_HANDLE, + attrs: &[(CK_ATTRIBUTE_TYPE, Vec)], + ) -> Result<(), ()> { + let slot_type = match self.sessions.get(&session) { + Some(slot_type) => *slot_type, + None => return Err(()), + }; + // If the search is for an attribute we don't support, no objects will match. This check + // saves us having to look through all of our objects. + for (attr, _) in attrs { + if !SUPPORTED_ATTRIBUTES.contains(attr) { + self.searches.insert(session, Vec::new()); + return Ok(()); + } + } + // When NSS wants to find all certificates or all private keys, it will perform a search + // with a particular set of attributes. This implementation uses these searches as an + // indication for the backend to re-scan for new objects from tokens that may have been + // inserted or certificates that may have been imported into the OS. Since these searches + // are relatively rare, this minimizes the impact of doing these re-scans. + if search_is_for_all_certificates_or_keys(attrs)? { + self.maybe_find_new_objects(); + } + let mut handles = Vec::new(); + for (handle, object) in &self.objects { + if object.matches(slot_type, attrs) { + handles.push(*handle); + } + } + self.searches.insert(session, handles); + Ok(()) + } + + /// Given a session and a maximum number of object handles to return, attempts to retrieve up to + /// that many objects from the corresponding search. Updates the search so those objects are not + /// returned repeatedly. `max_objects` must be non-zero. + pub fn search( + &mut self, + session: CK_SESSION_HANDLE, + max_objects: usize, + ) -> Result, ()> { + if max_objects == 0 { + return Err(()); + } + match self.searches.get_mut(&session) { + Some(search) => { + let split_at = if max_objects >= search.len() { + 0 + } else { + search.len() - max_objects + }; + let to_return = search.split_off(split_at); + if to_return.len() > max_objects { + error!( + "search trying to return too many handles: {} > {}", + to_return.len(), + max_objects + ); + return Err(()); + } + Ok(to_return) + } + None => Err(()), + } + } + + pub fn clear_search(&mut self, session: CK_SESSION_HANDLE) -> Result<(), ()> { + self.searches.remove(&session); + Ok(()) + } + + pub fn get_attributes( + &self, + object_handle: CK_OBJECT_HANDLE, + attr_types: Vec, + ) -> Result>>, ()> { + let object = match self.objects.get(&object_handle) { + Some(object) => object, + None => return Err(()), + }; + let mut results = Vec::with_capacity(attr_types.len()); + for attr_type in attr_types { + let result = match object.get_attribute(attr_type) { + Some(value) => Some(value.to_owned()), + None => None, + }; + results.push(result); + } + Ok(results) + } + + /// The way NSS uses PKCS #11 to sign data happens in two phases: setup and sign. This + /// implementation makes a note of which key is to be used (if it exists) during setup. When the + /// caller finishes with the sign operation, this implementation retrieves the key handle and + /// performs the signature. + pub fn start_sign( + &mut self, + session: CK_SESSION_HANDLE, + key_handle: CK_OBJECT_HANDLE, + params: Option, + ) -> Result<(), ()> { + if self.signs.contains_key(&session) { + return Err(()); + } + match self.objects.get(&key_handle) { + Some(Object::Key(_)) => {} + _ => return Err(()), + }; + self.signs.insert(session, (key_handle, params)); + Ok(()) + } + + pub fn get_signature_length( + &self, + session: CK_SESSION_HANDLE, + data: &[u8], + ) -> Result { + let (key_handle, params) = match self.signs.get(&session) { + Some((key_handle, params)) => (key_handle, params), + None => return Err(()), + }; + let key = match self.objects.get(&key_handle) { + Some(Object::Key(key)) => key, + _ => return Err(()), + }; + key.get_signature_length(data, params) + } + + pub fn sign(&mut self, session: CK_SESSION_HANDLE, data: &[u8]) -> Result, ()> { + // Performing the signature (via C_Sign, which is the only way we support) finishes the sign + // operation, so it needs to be removed here. + let (key_handle, params) = match self.signs.remove(&session) { + Some((key_handle, params)) => (key_handle, params), + None => return Err(()), + }; + let key = match self.objects.get(&key_handle) { + Some(Object::Key(key)) => key, + _ => return Err(()), + }; + key.sign(data, ¶ms) + } +} diff --git a/security/manager/ssl/osclientcerts/src/util.rs b/security/manager/ssl/osclientcerts/src/util.rs new file mode 100644 index 0000000000..fc176ee751 --- /dev/null +++ b/security/manager/ssl/osclientcerts/src/util.rs @@ -0,0 +1,463 @@ +/* -*- Mode: rust; rust-indent-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use byteorder::{BigEndian, NativeEndian, ReadBytesExt, WriteBytesExt}; +use std::convert::TryInto; + +/// Accessing fields of packed structs is unsafe (it may be undefined behavior if the field isn't +/// aligned). Since we're implementing a PKCS#11 module, we already have to trust the caller not to +/// give us bad data, so normally we would deal with this by adding an unsafe block. If we do that, +/// though, the compiler complains that the unsafe block is unnecessary. Thus, we use this macro to +/// annotate the unsafe block to silence the compiler. +macro_rules! unsafe_packed_field_access { + ($e:expr) => {{ + #[allow(unused_unsafe)] + let tmp = unsafe { $e }; + tmp + }}; +} + +#[cfg(target_os = "macos")] +pub const OID_BYTES_SECP256R1: &[u8] = + &[0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07]; +#[cfg(target_os = "macos")] +pub const OID_BYTES_SECP384R1: &[u8] = &[0x06, 0x05, 0x2b, 0x81, 0x04, 0x00, 0x22]; +#[cfg(target_os = "macos")] +pub const OID_BYTES_SECP521R1: &[u8] = &[0x06, 0x05, 0x2b, 0x81, 0x04, 0x00, 0x23]; + +// This is a helper function to take a value and lay it out in memory how +// PKCS#11 is expecting it. +pub fn serialize_uint>(value: T) -> Result, ()> { + let value_size = std::mem::size_of::(); + let mut value_buf = Vec::with_capacity(value_size); + let value_as_u64 = value.try_into().map_err(|_| ())?; + value_buf + .write_uint::(value_as_u64, value_size) + .map_err(|_| ())?; + Ok(value_buf) +} + +/// Given a slice of DER bytes representing an RSA public key, extracts the bytes of the modulus +/// as an unsigned integer. Also verifies that the public exponent is present (again as an +/// unsigned integer). Finally verifies that reading these values consumes the entirety of the +/// slice. +/// RSAPublicKey ::= SEQUENCE { +/// modulus INTEGER, -- n +/// publicExponent INTEGER -- e +/// } +pub fn read_rsa_modulus(public_key: &[u8]) -> Result, ()> { + let mut sequence = Sequence::new(public_key)?; + let modulus_value = sequence.read_unsigned_integer()?; + let _exponent = sequence.read_unsigned_integer()?; + if !sequence.at_end() { + return Err(()); + } + Ok(modulus_value.to_vec()) +} + +/// Given a slice of DER bytes representing a DigestInfo, extracts the bytes of the digest. +/// +/// DigestInfo ::= SEQUENCE { +/// digestAlgorithm DigestAlgorithmIdentifier, +/// digest Digest } +/// +/// DigestAlgorithmIdentifier ::= AlgorithmIdentifier +/// +/// AlgorithmIdentifier ::= SEQUENCE { +/// algorithm OBJECT IDENTIFIER, +/// parameters ANY DEFINED BY algorithm OPTIONAL } +/// +/// Digest ::= OCTET STRING +#[cfg(target_os = "windows")] +pub fn read_digest<'a>(digest_info: &'a [u8]) -> Result<&'a [u8], ()> { + let mut sequence = Sequence::new(digest_info)?; + let _ = sequence.read_sequence()?; + let digest = sequence.read_octet_string()?; + if !sequence.at_end() { + error!("read_digest: extra input"); + return Err(()); + } + Ok(digest) +} + +/// Given a slice of DER bytes representing an ECDSA signature, extracts the bytes of `r` and `s` +/// as unsigned integers. Also verifies that this consumes the entirety of the slice. +/// Ecdsa-Sig-Value ::= SEQUENCE { +/// r INTEGER, +/// s INTEGER } +#[cfg(target_os = "macos")] +pub fn read_ec_sig_point<'a>(signature: &'a [u8]) -> Result<(&'a [u8], &'a [u8]), ()> { + let mut sequence = Sequence::new(signature)?; + let r = sequence.read_unsigned_integer()?; + let s = sequence.read_unsigned_integer()?; + if !sequence.at_end() { + return Err(()); + } + Ok((r, s)) +} + +/// Given a slice of DER bytes representing an X.509 certificate, extracts the encoded serial +/// number. Does not verify that the remainder of the certificate is in any way well-formed. +/// Certificate ::= SEQUENCE { +/// tbsCertificate TBSCertificate, +/// signatureAlgorithm AlgorithmIdentifier, +/// signatureValue BIT STRING } +/// +/// TBSCertificate ::= SEQUENCE { +/// version [0] EXPLICIT Version DEFAULT v1, +/// serialNumber CertificateSerialNumber, +/// ... +/// +/// CertificateSerialNumber ::= INTEGER +pub fn read_encoded_serial_number(certificate: &[u8]) -> Result, ()> { + let mut certificate_sequence = Sequence::new(certificate)?; + let mut tbs_certificate_sequence = certificate_sequence.read_sequence()?; + let _version = tbs_certificate_sequence.read_tagged_value(0)?; + let serial_number = tbs_certificate_sequence.read_encoded_sequence_component(INTEGER)?; + Ok(serial_number) +} + +/// Helper macro for reading some bytes from a slice while checking the slice is long enough. +/// Returns a pair consisting of a slice of the bytes read and a slice of the rest of the bytes +/// from the original slice. +macro_rules! try_read_bytes { + ($data:ident, $len:expr) => {{ + if $data.len() < $len { + return Err(()); + } + $data.split_at($len) + }}; +} + +/// ASN.1 tag identifying an integer. +const INTEGER: u8 = 0x02; +/// ASN.1 tag identifying an octet string. +#[cfg(target_os = "windows")] +const OCTET_STRING: u8 = 0x04; +/// ASN.1 tag identifying a sequence. +const SEQUENCE: u8 = 0x10; +/// ASN.1 tag modifier identifying an item as constructed. +const CONSTRUCTED: u8 = 0x20; +/// ASN.1 tag modifier identifying an item as context-specific. +const CONTEXT_SPECIFIC: u8 = 0x80; + +/// A helper struct for reading items from a DER SEQUENCE (in this case, all sequences are +/// assumed to be CONSTRUCTED). +struct Sequence<'a> { + /// The contents of the SEQUENCE. + contents: Der<'a>, +} + +impl<'a> Sequence<'a> { + fn new(input: &'a [u8]) -> Result, ()> { + let mut der = Der::new(input); + let (_, _, sequence_bytes) = der.read_tlv(SEQUENCE | CONSTRUCTED)?; + // We're assuming we want to consume the entire input for now. + if !der.at_end() { + return Err(()); + } + Ok(Sequence { + contents: Der::new(sequence_bytes), + }) + } + + // TODO: we're not exhaustively validating this integer + fn read_unsigned_integer(&mut self) -> Result<&'a [u8], ()> { + let (_, _, bytes) = self.contents.read_tlv(INTEGER)?; + if bytes.is_empty() { + return Err(()); + } + // There may be a leading zero (we should also check that the first bit + // of the rest of the integer is set). + if bytes[0] == 0 && bytes.len() > 1 { + let (_, integer) = bytes.split_at(1); + Ok(integer) + } else { + Ok(bytes) + } + } + + #[cfg(target_os = "windows")] + fn read_octet_string(&mut self) -> Result<&'a [u8], ()> { + let (_, _, bytes) = self.contents.read_tlv(OCTET_STRING)?; + Ok(bytes) + } + + fn read_sequence(&mut self) -> Result, ()> { + let (_, _, sequence_bytes) = self.contents.read_tlv(SEQUENCE | CONSTRUCTED)?; + Ok(Sequence { + contents: Der::new(sequence_bytes), + }) + } + + fn read_tagged_value(&mut self, tag: u8) -> Result<&'a [u8], ()> { + let (_, _, tagged_value_bytes) = self + .contents + .read_tlv(CONTEXT_SPECIFIC | CONSTRUCTED | tag)?; + Ok(tagged_value_bytes) + } + + fn read_encoded_sequence_component(&mut self, tag: u8) -> Result, ()> { + let (tag, length, value) = self.contents.read_tlv(tag)?; + let mut encoded_component_bytes = length; + encoded_component_bytes.insert(0, tag); + encoded_component_bytes.extend_from_slice(value); + Ok(encoded_component_bytes) + } + + fn at_end(&self) -> bool { + self.contents.at_end() + } +} + +/// A helper struct for reading DER data. The contents are treated like a cursor, so its position +/// is updated as data is read. +struct Der<'a> { + contents: &'a [u8], +} + +impl<'a> Der<'a> { + fn new(contents: &'a [u8]) -> Der<'a> { + Der { contents } + } + + // In theory, a caller could encounter an error and try another operation, in which case we may + // be in an inconsistent state. As long as this implementation isn't exposed to code that would + // use it incorrectly (i.e. it stays in this module and we only expose a stateless API), it + // should be safe. + /// Given an expected tag, reads the next (tag, lengh, value) from the contents. Most + /// consumers will only be interested in the value, but some may want the entire encoded + /// contents, in which case the returned tuple can be concatenated. + fn read_tlv(&mut self, tag: u8) -> Result<(u8, Vec, &'a [u8]), ()> { + let contents = self.contents; + let (tag_read, rest) = try_read_bytes!(contents, 1); + if tag_read[0] != tag { + return Err(()); + } + let mut accumulated_length_bytes = Vec::with_capacity(4); + let (length1, rest) = try_read_bytes!(rest, 1); + accumulated_length_bytes.extend_from_slice(length1); + let (length, to_read_from) = if length1[0] < 0x80 { + (length1[0] as usize, rest) + } else if length1[0] == 0x81 { + let (length, rest) = try_read_bytes!(rest, 1); + accumulated_length_bytes.extend_from_slice(length); + if length[0] < 0x80 { + return Err(()); + } + (length[0] as usize, rest) + } else if length1[0] == 0x82 { + let (lengths, rest) = try_read_bytes!(rest, 2); + accumulated_length_bytes.extend_from_slice(lengths); + let length = (&mut &lengths[..]) + .read_u16::() + .map_err(|_| ())?; + if length < 256 { + return Err(()); + } + (length as usize, rest) + } else { + return Err(()); + }; + let (contents, rest) = try_read_bytes!(to_read_from, length); + self.contents = rest; + Ok((tag, accumulated_length_bytes, contents)) + } + + fn at_end(&self) -> bool { + self.contents.is_empty() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn der_test_empty_input() { + let input = Vec::new(); + let mut der = Der::new(&input); + assert!(der.read_tlv(INTEGER).is_err()); + } + + #[test] + fn der_test_no_length() { + let input = vec![INTEGER]; + let mut der = Der::new(&input); + assert!(der.read_tlv(INTEGER).is_err()); + } + + #[test] + fn der_test_empty_sequence() { + let input = vec![SEQUENCE, 0]; + let mut der = Der::new(&input); + let read_result = der.read_tlv(SEQUENCE); + assert!(read_result.is_ok()); + let (tag, length, sequence_bytes) = read_result.unwrap(); + assert_eq!(tag, SEQUENCE); + assert_eq!(length, vec![0]); + assert_eq!(sequence_bytes.len(), 0); + assert!(der.at_end()); + } + + #[test] + fn der_test_not_at_end() { + let input = vec![SEQUENCE, 0, 1]; + let mut der = Der::new(&input); + let read_result = der.read_tlv(SEQUENCE); + assert!(read_result.is_ok()); + let (tag, length, sequence_bytes) = read_result.unwrap(); + assert_eq!(tag, SEQUENCE); + assert_eq!(length, vec![0]); + assert_eq!(sequence_bytes.len(), 0); + assert!(!der.at_end()); + } + + #[test] + fn der_test_wrong_tag() { + let input = vec![SEQUENCE, 0]; + let mut der = Der::new(&input); + assert!(der.read_tlv(INTEGER).is_err()); + } + + #[test] + fn der_test_truncated_two_byte_length() { + let input = vec![SEQUENCE, 0x81]; + let mut der = Der::new(&input); + assert!(der.read_tlv(SEQUENCE).is_err()); + } + + #[test] + fn der_test_truncated_three_byte_length() { + let input = vec![SEQUENCE, 0x82, 1]; + let mut der = Der::new(&input); + assert!(der.read_tlv(SEQUENCE).is_err()); + } + + #[test] + fn der_test_truncated_data() { + let input = vec![SEQUENCE, 20, 1]; + let mut der = Der::new(&input); + assert!(der.read_tlv(SEQUENCE).is_err()); + } + + #[test] + fn der_test_sequence() { + let input = vec![ + SEQUENCE, 20, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 0, 0, + ]; + let mut der = Der::new(&input); + let result = der.read_tlv(SEQUENCE); + assert!(result.is_ok()); + let (tag, length, value) = result.unwrap(); + assert_eq!(tag, SEQUENCE); + assert_eq!(length, vec![20]); + assert_eq!( + value, + [1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 0, 0] + ); + assert!(der.at_end()); + } + + #[test] + fn der_test_not_shortest_two_byte_length_encoding() { + let input = vec![SEQUENCE, 0x81, 1, 1]; + let mut der = Der::new(&input); + assert!(der.read_tlv(SEQUENCE).is_err()); + } + + #[test] + fn der_test_not_shortest_three_byte_length_encoding() { + let input = vec![SEQUENCE, 0x82, 0, 1, 1]; + let mut der = Der::new(&input); + assert!(der.read_tlv(SEQUENCE).is_err()); + } + + #[test] + fn der_test_indefinite_length_unsupported() { + let input = vec![SEQUENCE, 0x80, 1, 2, 3, 0x00, 0x00]; + let mut der = Der::new(&input); + assert!(der.read_tlv(SEQUENCE).is_err()); + } + + #[test] + fn der_test_input_too_long() { + // This isn't valid DER (the contents of the SEQUENCE are truncated), but it demonstrates + // that we don't try to read too much if we're given a long length (and also that we don't + // support lengths 2^16 and up). + let input = vec![SEQUENCE, 0x83, 0x01, 0x00, 0x01, 1, 1, 1, 1]; + let mut der = Der::new(&input); + assert!(der.read_tlv(SEQUENCE).is_err()); + } + + #[test] + fn empty_input_fails() { + let empty = Vec::new(); + assert!(read_rsa_modulus(&empty).is_err()); + #[cfg(target_os = "macos")] + assert!(read_ec_sig_point(&empty).is_err()); + assert!(read_encoded_serial_number(&empty).is_err()); + } + + #[test] + fn empty_sequence_fails() { + let empty = vec![SEQUENCE | CONSTRUCTED]; + assert!(read_rsa_modulus(&empty).is_err()); + #[cfg(target_os = "macos")] + assert!(read_ec_sig_point(&empty).is_err()); + assert!(read_encoded_serial_number(&empty).is_err()); + } + + #[test] + fn test_read_rsa_modulus() { + let rsa_key = include_bytes!("../test/rsa.bin"); + let result = read_rsa_modulus(rsa_key); + assert!(result.is_ok()); + let modulus = result.unwrap(); + assert_eq!(modulus, include_bytes!("../test/modulus.bin").to_vec()); + } + + #[test] + fn test_read_serial_number() { + let certificate = include_bytes!("../test/certificate.bin"); + let result = read_encoded_serial_number(certificate); + assert!(result.is_ok()); + let serial_number = result.unwrap(); + assert_eq!( + serial_number, + &[ + 0x02, 0x14, 0x3f, 0xed, 0x7b, 0x43, 0x47, 0x8a, 0x53, 0x42, 0x5b, 0x0d, 0x50, 0xe1, + 0x37, 0x88, 0x2a, 0x20, 0x3f, 0x31, 0x17, 0x20 + ] + ); + } + + #[test] + #[cfg(target_os = "windows")] + fn test_read_digest() { + // SEQUENCE + // SEQUENCE + // OBJECT IDENTIFIER 2.16.840.1.101.3.4.2.1 sha-256 + // NULL + // OCTET STRING 1A7FCDB9A5F649F954885CFE145F3E93F0D1FA72BE980CC6EC82C70E1407C7D2 + let digest_info = [ + 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x1, 0x65, 0x03, 0x04, 0x02, + 0x01, 0x05, 0x00, 0x04, 0x20, 0x1a, 0x7f, 0xcd, 0xb9, 0xa5, 0xf6, 0x49, 0xf9, 0x54, + 0x88, 0x5c, 0xfe, 0x14, 0x5f, 0x3e, 0x93, 0xf0, 0xd1, 0xfa, 0x72, 0xbe, 0x98, 0x0c, + 0xc6, 0xec, 0x82, 0xc7, 0x0e, 0x14, 0x07, 0xc7, 0xd2, + ]; + let result = read_digest(&digest_info); + assert!(result.is_ok()); + let digest = result.unwrap(); + assert_eq!( + digest, + &[ + 0x1a, 0x7f, 0xcd, 0xb9, 0xa5, 0xf6, 0x49, 0xf9, 0x54, 0x88, 0x5c, 0xfe, 0x14, 0x5f, + 0x3e, 0x93, 0xf0, 0xd1, 0xfa, 0x72, 0xbe, 0x98, 0x0c, 0xc6, 0xec, 0x82, 0xc7, 0x0e, + 0x14, 0x07, 0xc7, 0xd2 + ] + ); + } +} -- cgit v1.2.3