diff options
| author | mo khan <mo@mokhan.ca> | 2025-07-10 13:11:11 -0600 |
|---|---|---|
| committer | mo khan <mo@mokhan.ca> | 2025-07-10 13:11:11 -0600 |
| commit | 01959b16a21b22b5df5f16569c2a8e8f92beecef (patch) | |
| tree | 32afa5d747c5466345c59ec52161a7cba3d6d755 /vendor/security-framework/src/os | |
| parent | ff30574117a996df332e23d1fb6f65259b316b5b (diff) | |
chore: vendor dependencies
Diffstat (limited to 'vendor/security-framework/src/os')
17 files changed, 3350 insertions, 0 deletions
diff --git a/vendor/security-framework/src/os/macos/access.rs b/vendor/security-framework/src/os/macos/access.rs new file mode 100644 index 00000000..1c41d85d --- /dev/null +++ b/vendor/security-framework/src/os/macos/access.rs @@ -0,0 +1,14 @@ +//! Access functionality. + +use core_foundation::base::TCFType; +use security_framework_sys::access::SecAccessGetTypeID; +use security_framework_sys::base::SecAccessRef; + +declare_TCFType! { + /// A type representing access settings. + SecAccess, SecAccessRef +} +impl_TCFType!(SecAccess, SecAccessRef, SecAccessGetTypeID); + +unsafe impl Sync for SecAccess {} +unsafe impl Send for SecAccess {} diff --git a/vendor/security-framework/src/os/macos/certificate.rs b/vendor/security-framework/src/os/macos/certificate.rs new file mode 100644 index 00000000..b50ee52d --- /dev/null +++ b/vendor/security-framework/src/os/macos/certificate.rs @@ -0,0 +1,267 @@ +//! OSX specific extensions to certificate functionality. + +use core_foundation::array::{CFArray, CFArrayIterator}; +use core_foundation::base::TCFType; +use core_foundation::base::ToVoid; +use core_foundation::data::CFData; +use core_foundation::dictionary::CFDictionary; +use core_foundation::error::CFError; +use core_foundation::string::CFString; +use security_framework_sys::certificate::*; +use std::os::raw::c_void; +use std::ptr; + +use crate::base::Error; +use crate::certificate::SecCertificate; +use crate::cvt; +use crate::key::SecKey; +use crate::os::macos::certificate_oids::CertificateOid; +use crate::os::macos::digest_transform::{Builder, DigestType}; + +/// An extension trait adding OSX specific functionality to `SecCertificate`. +pub trait SecCertificateExt { + /// Returns the common name associated with the certificate. + fn common_name(&self) -> Result<String, Error>; + + /// Returns the public key associated with the certificate. + #[cfg_attr(not(feature = "OSX_10_14"), deprecated(note = "Uses deprecated SecCertificateCopyPublicKey. Enable OSX_10_14 feature to avoid it"))] + fn public_key(&self) -> Result<SecKey, Error>; + + /// Returns the set of properties associated with the certificate. + /// + /// The `keys` argument can optionally be used to filter the properties loaded to an explicit + /// subset. + fn properties(&self, keys: Option<&[CertificateOid]>) + -> Result<CertificateProperties, CFError>; + + /// Returns the SHA-256 fingerprint of the certificate. + fn fingerprint(&self) -> Result<[u8; 32], CFError> { unimplemented!() } +} + +impl SecCertificateExt for SecCertificate { + fn common_name(&self) -> Result<String, Error> { + unsafe { + let mut string = ptr::null(); + cvt(SecCertificateCopyCommonName( + self.as_concrete_TypeRef(), + &mut string, + ))?; + Ok(CFString::wrap_under_create_rule(string).to_string()) + } + } + + #[cfg(feature = "OSX_10_14")] + fn public_key(&self) -> Result<SecKey, Error> { + unsafe { + let key = SecCertificateCopyKey(self.as_concrete_TypeRef()); + if key.is_null() { + return Err(Error::from_code(-26275)); + } + Ok(SecKey::wrap_under_create_rule(key)) + } + } + + #[cfg(not(feature = "OSX_10_14"))] + fn public_key(&self) -> Result<SecKey, Error> { + #[allow(deprecated)] + unsafe { + let mut key = ptr::null_mut(); + cvt(SecCertificateCopyPublicKey( + self.as_concrete_TypeRef(), + &mut key, + ))?; + Ok(SecKey::wrap_under_create_rule(key)) + } + } + + fn properties( + &self, + keys: Option<&[CertificateOid]>, + ) -> Result<CertificateProperties, CFError> { + unsafe { + let keys = keys.map(|oids| { + let oids = oids.iter().map(|oid| oid.to_str()).collect::<Vec<_>>(); + CFArray::from_CFTypes(&oids) + }); + + let keys = match keys { + Some(ref keys) => keys.as_concrete_TypeRef(), + None => ptr::null_mut(), + }; + + let mut error = ptr::null_mut(); + + let dictionary = SecCertificateCopyValues(self.as_concrete_TypeRef(), keys, &mut error); + + if error.is_null() { + Ok(CertificateProperties(CFDictionary::wrap_under_create_rule( + dictionary, + ))) + } else { + Err(CFError::wrap_under_create_rule(error)) + } + } + } + + /// Returns the SHA-256 fingerprint of the certificate. + fn fingerprint(&self) -> Result<[u8; 32], CFError> { + let data = CFData::from_buffer(&self.to_der()); + let hash = Builder::new() + .type_(DigestType::sha2()) + .length(256) + .execute(&data)?; + Ok(hash.bytes().try_into().unwrap()) + } +} + +/// Properties associated with a certificate. +pub struct CertificateProperties(CFDictionary); + +impl CertificateProperties { + /// Retrieves a specific property identified by its OID. + #[must_use] pub fn get(&self, oid: CertificateOid) -> Option<CertificateProperty> { + unsafe { + self.0.find(oid.as_ptr().cast::<c_void>()).map(|value| { + CertificateProperty(CFDictionary::wrap_under_get_rule(*value as *mut _)) + }) + } + } +} + +/// A property associated with a certificate. +pub struct CertificateProperty(CFDictionary); + +impl CertificateProperty { + /// Returns the label of this property. + #[must_use] + pub fn label(&self) -> CFString { + unsafe { + CFString::wrap_under_get_rule((*self.0.get(kSecPropertyKeyLabel.to_void())).cast()) + } + } + + /// Returns an enum of the underlying data for this property. + #[must_use] + pub fn get(&self) -> PropertyType { + unsafe { + let type_ = + CFString::wrap_under_get_rule(*self.0.get(kSecPropertyKeyType.to_void()) as *mut _); + let value = self.0.get(kSecPropertyKeyValue.to_void()); + + if type_ == CFString::wrap_under_get_rule(kSecPropertyTypeSection) { + PropertyType::Section(PropertySection(CFArray::wrap_under_get_rule( + (*value).cast(), + ))) + } else if type_ == CFString::wrap_under_get_rule(kSecPropertyTypeString) { + PropertyType::String(CFString::wrap_under_get_rule((*value).cast())) + } else { + PropertyType::__Unknown + } + } + } +} + +/// A "section" property. +/// +/// Sections are sequences of other properties. +pub struct PropertySection(CFArray<CFDictionary>); + +impl PropertySection { + /// Returns an iterator over the properties in this section. + #[inline(always)] + #[must_use] + pub fn iter(&self) -> PropertySectionIter<'_> { + PropertySectionIter(self.0.iter()) + } +} + +impl<'a> IntoIterator for &'a PropertySection { + type IntoIter = PropertySectionIter<'a>; + type Item = CertificateProperty; + + #[inline(always)] + fn into_iter(self) -> PropertySectionIter<'a> { + self.iter() + } +} + +/// An iterator over the properties in a section. +pub struct PropertySectionIter<'a>(CFArrayIterator<'a, CFDictionary>); + +impl<'a> Iterator for PropertySectionIter<'a> { + type Item = CertificateProperty; + + #[inline] + fn next(&mut self) -> Option<CertificateProperty> { + self.0.next().map(|t| CertificateProperty(t.clone())) + } + + #[inline(always)] + fn size_hint(&self) -> (usize, Option<usize>) { + self.0.size_hint() + } +} + +/// An enum of the various types of properties. +pub enum PropertyType { + /// A section. + Section(PropertySection), + /// A string. + String(CFString), + #[doc(hidden)] + __Unknown, +} + +#[cfg(test)] +mod test { + use super::*; + use crate::test::certificate; + use std::collections::HashMap; + + #[test] + fn common_name() { + let certificate = certificate(); + assert_eq!("foobar.com", p!(certificate.common_name())); + } + + #[test] + #[allow(deprecated)] + fn public_key() { + let certificate = certificate(); + p!(certificate.public_key()); + } + + #[test] + fn fingerprint() { + let certificate = certificate(); + let fingerprint = p!(certificate.fingerprint()); + assert_eq!( + "af9dd180a326ae08b37e6398f9262f8b9d4c55674a233a7c84975024f873655d", + hex::encode(fingerprint) + ); + } + + #[test] + fn signature_algorithm() { + let certificate = certificate(); + let properties = certificate + .properties(Some(&[CertificateOid::x509_v1_signature_algorithm()])) + .unwrap(); + let value = properties + .get(CertificateOid::x509_v1_signature_algorithm()) + .unwrap(); + let section = match value.get() { + PropertyType::Section(section) => section, + _ => panic!(), + }; + let properties = section + .iter() + .map(|p| (p.label().to_string(), p.get())) + .collect::<HashMap<_, _>>(); + let algorithm = match properties["Algorithm"] { + PropertyType::String(ref s) => s.to_string(), + _ => panic!(), + }; + assert_eq!(algorithm, "1.2.840.113549.1.1.5"); + } +} diff --git a/vendor/security-framework/src/os/macos/certificate_oids.rs b/vendor/security-framework/src/os/macos/certificate_oids.rs new file mode 100644 index 00000000..d820afe4 --- /dev/null +++ b/vendor/security-framework/src/os/macos/certificate_oids.rs @@ -0,0 +1,32 @@ +//! OIDs associated with certificate properties. +use core_foundation::base::TCFType; +use core_foundation::string::CFString; +use core_foundation_sys::string::CFStringRef; +use security_framework_sys::certificate_oids::kSecOIDX509V1SignatureAlgorithm; + +/// An identifier of a property of a certificate. +#[derive(Copy, Clone)] +pub struct CertificateOid(CFStringRef); + +#[allow(missing_docs)] +impl CertificateOid { + #[inline(always)] + #[must_use] + pub fn x509_v1_signature_algorithm() -> Self { + unsafe { Self(kSecOIDX509V1SignatureAlgorithm) } + } + + /// Returns the underlying raw pointer corresponding to this OID. + #[inline(always)] + #[must_use] + pub fn as_ptr(&self) -> CFStringRef { + self.0 + } + + /// Returns the string representation of the OID. + #[inline] + #[must_use] + pub fn to_str(&self) -> CFString { + unsafe { CFString::wrap_under_get_rule(self.0) } + } +} diff --git a/vendor/security-framework/src/os/macos/code_signing.rs b/vendor/security-framework/src/os/macos/code_signing.rs new file mode 100644 index 00000000..b42917c1 --- /dev/null +++ b/vendor/security-framework/src/os/macos/code_signing.rs @@ -0,0 +1,486 @@ +//! Code signing services. + +use std::{fmt::Debug, mem::MaybeUninit, str::FromStr}; + +use core_foundation::{ + base::{TCFType, TCFTypeRef, ToVoid}, + data::CFDataRef, + dictionary::CFMutableDictionary, + number::CFNumber, + string::{CFString, CFStringRef}, + url::CFURL, +}; +use libc::pid_t; +use security_framework_sys::code_signing::{ + kSecCSBasicValidateOnly, kSecCSCheckAllArchitectures, kSecCSCheckGatekeeperArchitectures, + kSecCSCheckNestedCode, kSecCSCheckTrustedAnchors, kSecCSConsiderExpiration, + kSecCSDoNotValidateExecutable, kSecCSDoNotValidateResources, kSecCSEnforceRevocationChecks, + kSecCSFullReport, kSecCSNoNetworkAccess, kSecCSQuickCheck, kSecCSReportProgress, + kSecCSRestrictSidebandData, kSecCSRestrictSymlinks, kSecCSRestrictToAppLike, + kSecCSSingleThreaded, kSecCSStrictValidate, kSecCSUseSoftwareSigningCert, kSecCSValidatePEH, + kSecGuestAttributeAudit, kSecGuestAttributePid, SecCodeCheckValidity, + SecCodeCopyGuestWithAttributes, SecCodeCopyPath, SecCodeCopySelf, SecCodeGetTypeID, SecCodeRef, + SecRequirementCreateWithString, SecRequirementGetTypeID, SecRequirementRef, + SecStaticCodeCheckValidity, SecStaticCodeCreateWithPath, SecStaticCodeGetTypeID, + SecStaticCodeRef, +}; + +use crate::{cvt, Result}; + +bitflags::bitflags! { + + /// Values that can be used in the flags parameter to most code signing + /// functions. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + pub struct Flags: u32 { + /// Use the default behaviour. + const NONE = 0; + + /// For multi-architecture (universal) Mach-O programs, validate all + /// architectures included. + const CHECK_ALL_ARCHITECTURES = kSecCSCheckAllArchitectures; + + /// Do not validate the contents of the main executable. + const DO_NOT_VALIDATE_EXECUTABLE = kSecCSDoNotValidateExecutable; + + /// Do not validate the presence and contents of all bundle resources + /// if any. + const DO_NOT_VALIDATE_RESOURCES = kSecCSDoNotValidateResources; + + /// Do not validate either the main executable or the bundle resources, + /// if any. + const BASIC_VALIDATE_ONLY = kSecCSBasicValidateOnly; + + /// For code in bundle form, locate and recursively check embedded code. + const CHECK_NESTED_CODE = kSecCSCheckNestedCode; + + /// Perform additional checks to ensure the validity of code in bundle + /// form. + const STRICT_VALIDATE = kSecCSStrictValidate; + + /// Apple have not documented this flag. + const FULL_REPORT = kSecCSFullReport; + + /// Apple have not documented this flag. + const CHECK_GATEKEEPER_ARCHITECTURES = kSecCSCheckGatekeeperArchitectures; + + /// Apple have not documented this flag. + const RESTRICT_SYMLINKS = kSecCSRestrictSymlinks; + + /// Apple have not documented this flag. + const RESTRICT_TO_APP_LIKE = kSecCSRestrictToAppLike; + + /// Apple have not documented this flag. + const RESTRICT_SIDEBAND_DATA = kSecCSRestrictSidebandData; + + /// Apple have not documented this flag. + const USE_SOFTWARE_SIGNING_CERT = kSecCSUseSoftwareSigningCert; + + /// Apple have not documented this flag. + const VALIDATE_PEH = kSecCSValidatePEH; + + /// Apple have not documented this flag. + const SINGLE_THREADED = kSecCSSingleThreaded; + + /// Apple have not documented this flag. + const QUICK_CHECK = kSecCSQuickCheck; + + /// Apple have not documented this flag. + const CHECK_TRUSTED_ANCHORS = kSecCSCheckTrustedAnchors; + + /// Apple have not documented this flag. + const REPORT_PROGRESS = kSecCSReportProgress; + + /// Apple have not documented this flag. + const NO_NETWORK_ACCESS = kSecCSNoNetworkAccess; + + /// Apple have not documented this flag. + const ENFORCE_REVOCATION_CHECKS = kSecCSEnforceRevocationChecks; + + /// Apple have not documented this flag. + const CONSIDER_EXPIRATION = kSecCSConsiderExpiration; + } +} + +impl Default for Flags { + #[inline(always)] + fn default() -> Self { + Self::NONE + } +} + +/// A helper to create guest attributes, which are normally passed as a +/// `CFDictionary` with varying types. +pub struct GuestAttributes { + inner: CFMutableDictionary, +} + +impl GuestAttributes { + // Not implemented: + // - architecture + // - canonical + // - dynamic code + // - dynamic code info plist + // - hash + // - mach port + // - sub-architecture + + /// Creates a new, empty `GuestAttributes`. You must add values to it in + /// order for it to be of any use. + #[must_use] + pub fn new() -> Self { + Self { + inner: CFMutableDictionary::new(), + } + } + + /// The guest's audit token. + pub fn set_audit_token(&mut self, token: CFDataRef) { + let key = unsafe { CFString::wrap_under_get_rule(kSecGuestAttributeAudit) }; + self.inner.add(&key.as_CFTypeRef(), &token.to_void()); + } + + /// The guest's pid. + pub fn set_pid(&mut self, pid: pid_t) { + let key = unsafe { CFString::wrap_under_get_rule(kSecGuestAttributePid) }; + let pid = CFNumber::from(pid); + self.inner.add(&key.as_CFTypeRef(), &pid.as_CFTypeRef()); + } + + /// Support for arbirtary guest attributes. + pub fn set_other<V: ToVoid<V>>(&mut self, key: CFStringRef, value: V) { + self.inner.add(&key.as_void_ptr(), &value.to_void()); + } +} + +impl Default for GuestAttributes { + fn default() -> Self { + Self::new() + } +} + +declare_TCFType! { + /// A code object representing signed code running on the system. + SecRequirement, SecRequirementRef +} +impl_TCFType!(SecRequirement, SecRequirementRef, SecRequirementGetTypeID); + +impl FromStr for SecRequirement { + type Err = crate::base::Error; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + let text = CFString::new(s); + let mut requirement = MaybeUninit::uninit(); + + unsafe { + cvt(SecRequirementCreateWithString( + text.as_concrete_TypeRef(), + 0, + requirement.as_mut_ptr(), + ))?; + + Ok(Self::wrap_under_create_rule(requirement.assume_init())) + } + } +} + +declare_TCFType! { + /// A code object representing signed code running on the system. + SecCode, SecCodeRef +} +impl_TCFType!(SecCode, SecCodeRef, SecCodeGetTypeID); + +impl Debug for SecCode { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("SecCode") + } +} + +impl SecCode { + /// Retrieves the code object for the code making the call. + pub fn for_self(flags: Flags) -> Result<Self> { + let mut code = MaybeUninit::uninit(); + + unsafe { + cvt(SecCodeCopySelf(flags.bits(), code.as_mut_ptr()))?; + Ok(Self::wrap_under_create_rule(code.assume_init())) + } + } + + /// Performs dynamic validation of signed code. + pub fn check_validity(&self, flags: Flags, requirement: &SecRequirement) -> Result<()> { + unsafe { + cvt(SecCodeCheckValidity( + self.as_concrete_TypeRef(), + flags.bits(), + requirement.as_concrete_TypeRef(), + )) + } + } + + /// Asks a code host to identify one of its guests given + /// the type and value of specific attributes of the guest code. + /// + /// If `host` is `None` then the code signing root of trust (currently, the + // system kernel) should be used as the code host. + pub fn copy_guest_with_attribues( + host: Option<&SecCode>, + attrs: &GuestAttributes, + flags: Flags, + ) -> Result<SecCode> { + let mut code = MaybeUninit::uninit(); + + let host = match host { + Some(host) => host.as_concrete_TypeRef(), + None => std::ptr::null_mut(), + }; + + unsafe { + cvt(SecCodeCopyGuestWithAttributes( + host, + attrs.inner.as_concrete_TypeRef(), + flags.bits(), + code.as_mut_ptr(), + ))?; + + Ok(SecCode::wrap_under_create_rule(code.assume_init())) + } + } + + /// Retrieves the location on disk of signed code, given a code or static + /// code object. + pub fn path(&self, flags: Flags) -> Result<CFURL> { + let mut url = MaybeUninit::uninit(); + + // The docs say we can pass a SecCodeRef instead of a SecStaticCodeRef. + unsafe { + cvt(SecCodeCopyPath( + self.as_CFTypeRef() as _, + flags.bits(), + url.as_mut_ptr(), + ))?; + + Ok(CFURL::wrap_under_create_rule(url.assume_init())) + } + } +} + +declare_TCFType! { + /// A static code object representing signed code on disk. + SecStaticCode, SecStaticCodeRef +} +impl_TCFType!(SecStaticCode, SecStaticCodeRef, SecStaticCodeGetTypeID); + +impl SecStaticCode { + /// Creates a static code object representing the code at a specified file + /// system path. + pub fn from_path(path: &CFURL, flags: Flags) -> Result<Self> { + let mut code = MaybeUninit::uninit(); + + unsafe { + cvt(SecStaticCodeCreateWithPath( + path.as_concrete_TypeRef(), + flags.bits(), + code.as_mut_ptr(), + ))?; + + Ok(Self::wrap_under_create_rule(code.assume_init())) + } + } + + /// Retrieves the location on disk of signed code, given a code or static + /// code object. + pub fn path(&self, flags: Flags) -> Result<CFURL> { + let mut url = MaybeUninit::uninit(); + + // The docs say we can pass a SecCodeRef instead of a SecStaticCodeRef. + unsafe { + cvt(SecCodeCopyPath( + self.as_concrete_TypeRef(), + flags.bits(), + url.as_mut_ptr(), + ))?; + + Ok(CFURL::wrap_under_create_rule(url.assume_init())) + } + } + + /// Performs dynamic validation of signed code. + pub fn check_validity(&self, flags: Flags, requirement: &SecRequirement) -> Result<()> { + unsafe { + cvt(SecStaticCodeCheckValidity( + self.as_concrete_TypeRef(), + flags.bits(), + requirement.as_concrete_TypeRef(), + )) + } + } +} + +#[cfg(test)] +mod test { + use super::*; + use core_foundation::data::CFData; + use libc::{c_uint, c_void, KERN_SUCCESS}; + + #[test] + fn path_to_static_code_and_back() { + let path = CFURL::from_path("/bin/bash", false).unwrap(); + let code = SecStaticCode::from_path(&path, Flags::NONE).unwrap(); + assert_eq!(code.path(Flags::NONE).unwrap(), path); + } + + #[test] + fn self_to_path() { + let path = CFURL::from_path(std::env::current_exe().unwrap(), false).unwrap(); + let code = SecCode::for_self(Flags::NONE).unwrap(); + assert_eq!(code.path(Flags::NONE).unwrap(), path); + } + + #[test] + fn bash_is_signed_by_apple() { + let path = CFURL::from_path("/bin/bash", false).unwrap(); + let code = SecStaticCode::from_path(&path, Flags::NONE).unwrap(); + let requirement: SecRequirement = "anchor apple".parse().unwrap(); + code.check_validity(Flags::NONE, &requirement).unwrap(); + } + + #[cfg(target_arch = "aarch64")] + #[test] + fn self_is_not_signed_by_apple() { + let code = SecCode::for_self(Flags::NONE).unwrap(); + let requirement: SecRequirement = "anchor apple".parse().unwrap(); + + assert_eq!( + code.check_validity(Flags::NONE, &requirement) + .unwrap_err() + .code(), + // "code failed to satisfy specified code requirement(s)" + -67050 + ); + } + + #[cfg(not(target_arch = "aarch64"))] + #[test] + fn self_is_not_signed_by_apple() { + let code = SecCode::for_self(Flags::NONE).unwrap(); + let requirement: SecRequirement = "anchor apple".parse().unwrap(); + + assert_eq!( + code.check_validity(Flags::NONE, &requirement) + .unwrap_err() + .code(), + // "code object is not signed at all" + -67062 + ); + } + + #[test] + fn copy_kernel_guest_with_launchd_pid() { + let mut attrs = GuestAttributes::new(); + attrs.set_pid(1); + + assert_eq!( + SecCode::copy_guest_with_attribues(None, &attrs, Flags::NONE) + .unwrap() + .path(Flags::NONE) + .unwrap() + .get_string() + .to_string(), + "file:///sbin/launchd" + ); + } + + #[test] + fn copy_current_guest_with_launchd_pid() { + let host_code = SecCode::for_self(Flags::NONE).unwrap(); + + let mut attrs = GuestAttributes::new(); + attrs.set_pid(1); + + assert_eq!( + SecCode::copy_guest_with_attribues(Some(&host_code), &attrs, Flags::NONE) + .unwrap_err() + .code(), + // "host has no guest with the requested attributes" + -67065 + ); + } + + #[test] + fn copy_kernel_guest_with_unmatched_pid() { + let mut attrs = GuestAttributes::new(); + attrs.set_pid(999_999_999); + + assert_eq!( + SecCode::copy_guest_with_attribues(None, &attrs, Flags::NONE) + .unwrap_err() + .code(), + // "UNIX[No such process]" + 100003 + ); + } + + #[test] + fn copy_kernel_guest_with_current_token() { + let mut token: [u8; 32] = [0; 32]; + let mut token_len = 32u32; + + enum OpaqueTaskName {} + + extern "C" { + fn mach_task_self() -> *const OpaqueTaskName; + fn task_info( + task_name: *const OpaqueTaskName, + task_flavor: u32, + out: *mut c_void, + out_len: *mut u32, + ) -> i32; + } + + const TASK_AUDIT_TOKEN: c_uint = 15; + + let result = unsafe { + task_info( + mach_task_self(), + TASK_AUDIT_TOKEN, + token.as_mut_ptr().cast::<c_void>(), + &mut token_len, + ) + }; + + assert_eq!(result, KERN_SUCCESS); + + let token_data = CFData::from_buffer(&token); + + let mut attrs = GuestAttributes::new(); + attrs.set_audit_token(token_data.as_concrete_TypeRef()); + + assert_eq!( + SecCode::copy_guest_with_attribues(None, &attrs, Flags::NONE) + .unwrap() + .path(Flags::NONE) + .unwrap() + .to_path() + .unwrap(), + std::env::current_exe().unwrap() + ); + } + + #[test] + fn copy_kernel_guest_with_unmatched_token() { + let token: [u8; 32] = [0; 32]; + let token_data = CFData::from_buffer(&token); + + let mut attrs = GuestAttributes::new(); + attrs.set_audit_token(token_data.as_concrete_TypeRef()); + + assert_eq!( + SecCode::copy_guest_with_attribues(None, &attrs, Flags::NONE) + .unwrap_err() + .code(), + // "UNIX[No such process]" + 100003 + ); + } +} diff --git a/vendor/security-framework/src/os/macos/digest_transform.rs b/vendor/security-framework/src/os/macos/digest_transform.rs new file mode 100644 index 00000000..c086ed1a --- /dev/null +++ b/vendor/security-framework/src/os/macos/digest_transform.rs @@ -0,0 +1,194 @@ +//! Digest Transform support + +use core_foundation::base::{CFIndex, TCFType}; +use core_foundation::data::CFData; +use core_foundation::error::CFError; +use core_foundation::string::CFString; +use core_foundation_sys::base::CFTypeRef; +use core_foundation_sys::data::CFDataRef; +use core_foundation_sys::string::CFStringRef; +use security_framework_sys::digest_transform::*; +use security_framework_sys::transform::kSecTransformInputAttributeName; +use std::ptr; + +use crate::os::macos::transform::SecTransform; + +#[derive(Debug, Copy, Clone)] +/// A type of digest. +pub struct DigestType(CFStringRef); + +#[allow(missing_docs)] +impl DigestType { + #[inline(always)] + #[must_use] + pub fn hmac_md5() -> Self { + unsafe { Self(kSecDigestHMACMD5) } + } + + #[inline(always)] + #[must_use] + pub fn hmac_sha1() -> Self { + unsafe { Self(kSecDigestHMACSHA1) } + } + + #[inline(always)] + #[must_use] + pub fn hmac_sha2() -> Self { + unsafe { Self(kSecDigestHMACSHA2) } + } + + #[inline(always)] + #[must_use] + pub fn md2() -> Self { + unsafe { Self(kSecDigestMD2) } + } + + #[inline(always)] + #[must_use] + pub fn md4() -> Self { + unsafe { Self(kSecDigestMD4) } + } + + #[inline(always)] + #[must_use] + pub fn md5() -> Self { + unsafe { Self(kSecDigestMD5) } + } + + #[inline(always)] + #[must_use] + pub fn sha1() -> Self { + unsafe { Self(kSecDigestSHA1) } + } + + #[inline(always)] + #[must_use] + pub fn sha2() -> Self { + unsafe { Self(kSecDigestSHA2) } + } + + #[inline(always)] + fn to_type(self) -> CFTypeRef { + self.0 as CFTypeRef + } +} + +/// A builder for digest transform operations. +pub struct Builder { + digest_type: Option<DigestType>, + digest_length: Option<CFIndex>, + hmac_key: Option<CFData>, +} + +impl Default for Builder { + #[inline(always)] + fn default() -> Self { + Self::new() + } +} + +impl Builder { + /// Returns a new builder with default settings. + #[inline(always)] + #[must_use] + pub fn new() -> Self { + Self { + digest_type: None, + digest_length: None, + hmac_key: None, + } + } + + /// Sets the type of digest to perform. + /// + /// If not set, an appropriate digest will be selected for you. + #[inline] + pub fn type_(&mut self, digest_type: DigestType) -> &mut Self { + self.digest_type = Some(digest_type); + self + } + + /// Sets the output length of the digest. + /// + /// If not set, an appropriate length will be selected for you. Some digest + /// types only support specific output lengths. + #[inline] + pub fn length(&mut self, digest_length: CFIndex) -> &mut Self { + self.digest_length = Some(digest_length); + self + } + + /// Sets the key used for HMAC digests. + /// + /// Only applies to `HmacMd5`, `HmacSha1`, and `HmacSha2` digests. + #[inline] + pub fn hmac_key(&mut self, hmac_key: CFData) -> &mut Self { + self.hmac_key = Some(hmac_key); + self + } + + /// Computes the digest of the data. + pub fn execute(&self, data: &CFData) -> Result<CFData, CFError> { + unsafe { + let digest_type = match self.digest_type { + Some(ref digest_type) => digest_type.to_type(), + None => ptr::null(), + }; + + let digest_length = self.digest_length.unwrap_or(0); + + let mut error = ptr::null_mut(); + let transform = SecDigestTransformCreate(digest_type, digest_length, &mut error); + if transform.is_null() { + return Err(CFError::wrap_under_create_rule(error)); + } + let mut transform = SecTransform::wrap_under_create_rule(transform); + + if let Some(ref hmac_key) = self.hmac_key { + let key = CFString::wrap_under_get_rule(kSecDigestHMACKeyAttribute); + transform.set_attribute(&key, hmac_key)?; + } + + let key = CFString::wrap_under_get_rule(kSecTransformInputAttributeName); + transform.set_attribute(&key, data)?; + + let result = transform.execute()?; + Ok(CFData::wrap_under_get_rule( + result.as_CFTypeRef() as CFDataRef + )) + } + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn md5() { + let data = CFData::from_buffer("The quick brown fox jumps over the lazy dog".as_bytes()); + let hash = Builder::new() + .type_(DigestType::md5()) + .execute(&data) + .unwrap(); + assert_eq!( + hex::encode(hash.bytes()), + "9e107d9d372bb6826bd81d3542a419d6" + ); + } + + #[test] + fn hmac_sha1() { + let data = CFData::from_buffer("The quick brown fox jumps over the lazy dog".as_bytes()); + let key = CFData::from_buffer(b"key"); + let hash = Builder::new() + .type_(DigestType::hmac_sha1()) + .hmac_key(key) + .execute(&data) + .unwrap(); + assert_eq!( + hex::encode(hash.bytes()), + "de7c9b85b8b78aa6bc8a7a36f70a90701c9db4d9" + ); + } +} diff --git a/vendor/security-framework/src/os/macos/encrypt_transform.rs b/vendor/security-framework/src/os/macos/encrypt_transform.rs new file mode 100644 index 00000000..7b9736da --- /dev/null +++ b/vendor/security-framework/src/os/macos/encrypt_transform.rs @@ -0,0 +1,254 @@ +//! Encryption and Decryption transform support. + +use core_foundation::base::TCFType; +use core_foundation::data::CFData; +use core_foundation::error::CFError; +use core_foundation::string::CFString; +use core_foundation_sys::data::CFDataRef; +use core_foundation_sys::string::CFStringRef; +use security_framework_sys::encrypt_transform::*; +use security_framework_sys::transform::kSecTransformInputAttributeName; +use std::ptr; + +use crate::key::SecKey; +use crate::os::macos::transform::SecTransform; + +#[derive(Debug, Copy, Clone)] +/// The padding scheme to use for encryption. +pub struct Padding(CFStringRef); + +impl Padding { + /// Do not pad. + #[inline(always)] + #[must_use] + pub fn none() -> Self { + unsafe { Self(kSecPaddingNoneKey) } + } + + /// Use PKCS#1 padding. + #[inline(always)] + #[must_use] + pub fn pkcs1() -> Self { + unsafe { Self(kSecPaddingPKCS1Key) } + } + + /// Use PKCS#5 padding. + #[inline(always)] + #[must_use] + pub fn pkcs5() -> Self { + unsafe { Self(kSecPaddingPKCS5Key) } + } + + /// Use PKCS#7 padding. + #[inline(always)] + #[must_use] + pub fn pkcs7() -> Self { + unsafe { Self(kSecPaddingPKCS7Key) } + } + + /// Use OAEP padding. + #[inline(always)] + #[must_use] + pub fn oaep() -> Self { + unsafe { Self(kSecPaddingOAEPKey) } + } + + #[inline] + fn to_str(self) -> CFString { + unsafe { CFString::wrap_under_get_rule(self.0) } + } +} + +/// The cipher mode to use. +/// +/// Only applies to AES encryption. +#[derive(Debug, Copy, Clone)] +pub struct Mode(CFStringRef); + +#[allow(missing_docs)] +impl Mode { + #[inline(always)] + #[must_use] + pub fn none() -> Self { + unsafe { Self(kSecModeNoneKey) } + } + + #[inline(always)] + #[must_use] + pub fn ecb() -> Self { + unsafe { Self(kSecModeECBKey) } + } + + #[inline(always)] + #[must_use] + pub fn cbc() -> Self { + unsafe { Self(kSecModeCBCKey) } + } + + #[inline(always)] + #[must_use] + pub fn cfb() -> Self { + unsafe { Self(kSecModeCFBKey) } + } + + #[inline(always)] + #[must_use] + pub fn ofb() -> Self { + unsafe { Self(kSecModeOFBKey) } + } + + fn to_str(self) -> CFString { + unsafe { CFString::wrap_under_get_rule(self.0) } + } +} + +/// A builder for encryption and decryption transform operations. +#[derive(Default)] +pub struct Builder { + padding: Option<Padding>, + mode: Option<Mode>, + iv: Option<CFData>, +} + +impl Builder { + /// Creates a new `Builder` with a default configuration. + #[inline(always)] + #[must_use] + pub fn new() -> Self { + Self::default() + } + + /// Selects the padding scheme to use. + /// + /// If not set, an appropriate scheme will be selected for you. + #[inline(always)] + pub fn padding(&mut self, padding: Padding) -> &mut Self { + self.padding = Some(padding); + self + } + + /// Selects the encryption mode to use. + /// + /// If not set, an appropriate mode will be selected for you. + #[inline(always)] + pub fn mode(&mut self, mode: Mode) -> &mut Self { + self.mode = Some(mode); + self + } + + /// Sets the initialization vector to use. + /// + /// If not set, an appropriate value will be supplied for you. + #[inline(always)] + pub fn iv(&mut self, iv: CFData) -> &mut Self { + self.iv = Some(iv); + self + } + + /// Encrypts data with a provided key. + pub fn encrypt(&self, key: &SecKey, data: &CFData) -> Result<CFData, CFError> { + unsafe { + let mut error = ptr::null_mut(); + let transform = SecEncryptTransformCreate(key.as_concrete_TypeRef(), &mut error); + if transform.is_null() { + return Err(CFError::wrap_under_create_rule(error)); + } + let transform = SecTransform::wrap_under_create_rule(transform); + + self.finish(transform, data) + } + } + + /// Decrypts data with a provided key. + pub fn decrypt(&self, key: &SecKey, data: &CFData) -> Result<CFData, CFError> { + unsafe { + let mut error = ptr::null_mut(); + let transform = SecDecryptTransformCreate(key.as_concrete_TypeRef(), &mut error); + if transform.is_null() { + return Err(CFError::wrap_under_create_rule(error)); + } + let transform = SecTransform::wrap_under_create_rule(transform); + + self.finish(transform, data) + } + } + + fn finish(&self, mut transform: SecTransform, data: &CFData) -> Result<CFData, CFError> { + unsafe { + if let Some(ref padding) = self.padding { + let key = CFString::wrap_under_get_rule(kSecPaddingKey); + transform.set_attribute(&key, &padding.to_str())?; + } + + if let Some(ref mode) = self.mode { + let key = CFString::wrap_under_get_rule(kSecEncryptionMode); + transform.set_attribute(&key, &mode.to_str())?; + } + + if let Some(ref iv) = self.iv { + let key = CFString::wrap_under_get_rule(kSecIVKey); + transform.set_attribute(&key, iv)?; + } + + let key = CFString::wrap_under_get_rule(kSecTransformInputAttributeName); + transform.set_attribute(&key, data)?; + + let result = transform.execute()?; + Ok(CFData::wrap_under_get_rule( + result.as_CFTypeRef() as CFDataRef + )) + } + } +} + +#[cfg(test)] +mod test { + use hex::FromHex; + + use super::*; + use crate::os::macos::item::KeyType; + use crate::os::macos::key::SecKeyExt; + + #[test] + fn cbc_mmt_256() { + // test 9 + let key = "87725bd43a45608814180773f0e7ab95a3c859d83a2130e884190e44d14c6996"; + let iv = "e49651988ebbb72eb8bb80bb9abbca34"; + let ciphertext = "5b97a9d423f4b97413f388d9a341e727bb339f8e18a3fac2f2fb85abdc8f135deb30054a\ + 1afdc9b6ed7da16c55eba6b0d4d10c74e1d9a7cf8edfaeaa684ac0bd9f9d24ba674955c7\ + 9dc6be32aee1c260b558ff07e3a4d49d24162011ff254db8be078e8ad07e648e6bf56793\ + 76cb4321a5ef01afe6ad8816fcc7634669c8c4389295c9241e45fff39f3225f7745032da\ + eebe99d4b19bcb215d1bfdb36eda2c24"; + let plaintext = "bfe5c6354b7a3ff3e192e05775b9b75807de12e38a626b8bf0e12d5fff78e4f1775aa7d79\ + 2d885162e66d88930f9c3b2cdf8654f56972504803190386270f0aa43645db187af41fcea\ + 639b1f8026ccdd0c23e0de37094a8b941ecb7602998a4b2604e69fc04219585d854600e0a\ + d6f99a53b2504043c08b1c3e214d17cde053cbdf91daa999ed5b47c37983ba3ee254bc5c7\ + 93837daaa8c85cfc12f7f54f699f"; + + let key = Vec::<u8>::from_hex(key).unwrap(); + let key = CFData::from_buffer(&key); + let key = SecKey::from_data(KeyType::aes(), &key).unwrap(); + + let iv = Vec::<u8>::from_hex(iv).unwrap(); + + let ciphertext = Vec::<u8>::from_hex(ciphertext).unwrap(); + + let plaintext = Vec::<u8>::from_hex(plaintext).unwrap(); + + let decrypted = Builder::new() + .padding(Padding::none()) + .iv(CFData::from_buffer(&iv)) + .decrypt(&key, &CFData::from_buffer(&ciphertext)) + .unwrap(); + + assert_eq!(plaintext, decrypted.bytes()); + + let encrypted = Builder::new() + .padding(Padding::none()) + .iv(CFData::from_buffer(&iv)) + .encrypt(&key, &CFData::from_buffer(&plaintext)) + .unwrap(); + + assert_eq!(ciphertext, encrypted.bytes()); + } +} diff --git a/vendor/security-framework/src/os/macos/identity.rs b/vendor/security-framework/src/os/macos/identity.rs new file mode 100644 index 00000000..0f8da464 --- /dev/null +++ b/vendor/security-framework/src/os/macos/identity.rs @@ -0,0 +1,85 @@ +//! OSX specific extensions to identity functionality. +use core_foundation::array::CFArray; +use core_foundation::base::TCFType; +use security_framework_sys::identity::SecIdentityCreateWithCertificate; +use std::ptr; + +use crate::base::Result; +use crate::certificate::SecCertificate; +use crate::cvt; +use crate::identity::SecIdentity; +use crate::os::macos::keychain::SecKeychain; + +/// An extension trait adding OSX specific functionality to `SecIdentity`. +pub trait SecIdentityExt { + /// Creates an identity corresponding to a certificate, looking in the + /// provided keychains for the corresponding private key. + /// + /// To search the default keychains, use an empty slice for `keychains`. + /// + /// <https://developer.apple.com/documentation/security/1401160-secidentitycreatewithcertificate> + fn with_certificate( + keychains: &[SecKeychain], + certificate: &SecCertificate, + ) -> Result<SecIdentity>; +} + +impl SecIdentityExt for SecIdentity { + fn with_certificate(keychains: &[SecKeychain], certificate: &SecCertificate) -> Result<Self> { + let keychains = CFArray::from_CFTypes(keychains); + unsafe { + let mut identity = ptr::null_mut(); + cvt(SecIdentityCreateWithCertificate( + if keychains.len() > 0 {keychains.as_CFTypeRef()} else {ptr::null()}, + certificate.as_concrete_TypeRef(), + &mut identity, + ))?; + Ok(Self::wrap_under_create_rule(identity)) + } + } +} + +#[cfg(test)] +mod test { + use tempfile::tempdir; + + use super::*; + use crate::os::macos::certificate::SecCertificateExt; + use crate::os::macos::import_export::ImportOptions; + use crate::os::macos::keychain::CreateOptions; + use crate::os::macos::test::identity; + use crate::test; + + #[test] + fn certificate() { + let dir = p!(tempdir()); + let identity = identity(dir.path()); + let certificate = p!(identity.certificate()); + assert_eq!("foobar.com", p!(certificate.common_name())); + } + + #[test] + fn private_key() { + let dir = p!(tempdir()); + let identity = identity(dir.path()); + p!(identity.private_key()); + } + + #[test] + fn with_certificate() { + let dir = p!(tempdir()); + + let mut keychain = p!(CreateOptions::new() + .password("foobar") + .create(dir.path().join("test.keychain"))); + + let key = include_bytes!("../../../test/server.key"); + p!(ImportOptions::new() + .filename("server.key") + .keychain(&mut keychain) + .import(key)); + + let cert = test::certificate(); + p!(SecIdentity::with_certificate(&[keychain], &cert)); + } +} diff --git a/vendor/security-framework/src/os/macos/import_export.rs b/vendor/security-framework/src/os/macos/import_export.rs new file mode 100644 index 00000000..830d9483 --- /dev/null +++ b/vendor/security-framework/src/os/macos/import_export.rs @@ -0,0 +1,344 @@ +//! OSX specific extensions to import/export functionality. + +use core_foundation::array::CFArray; +use core_foundation::base::{CFType, TCFType}; +use core_foundation::data::CFData; +use core_foundation::string::CFString; +use security_framework_sys::base::errSecSuccess; +use security_framework_sys::import_export::*; +use std::ptr; +use std::str::FromStr; + +use crate::base::{Error, Result}; +use crate::certificate::SecCertificate; +use crate::identity::SecIdentity; +use crate::import_export::Pkcs12ImportOptions; +use crate::key::SecKey; +use crate::os::macos::access::SecAccess; +use crate::os::macos::keychain::SecKeychain; + +/// An extension trait adding OSX specific functionality to `Pkcs12ImportOptions`. +pub trait Pkcs12ImportOptionsExt { + /// Specifies the keychain in which to import the identity. + /// + /// If this is not called, the default keychain will be used. + fn keychain(&mut self, keychain: SecKeychain) -> &mut Self; + + /// Specifies the access control to be associated with the identity. + fn access(&mut self, access: SecAccess) -> &mut Self; +} + +impl Pkcs12ImportOptionsExt for Pkcs12ImportOptions { + #[inline(always)] + fn keychain(&mut self, keychain: SecKeychain) -> &mut Self { + crate::Pkcs12ImportOptionsInternals::keychain(self, keychain) + } + + #[inline(always)] + fn access(&mut self, access: SecAccess) -> &mut Self { + crate::Pkcs12ImportOptionsInternals::access(self, access) + } +} + +/// A builder type to import Security Framework types from serialized formats. +#[derive(Default)] +pub struct ImportOptions<'a> { + filename: Option<CFString>, + passphrase: Option<CFType>, + secure_passphrase: bool, + no_access_control: bool, + alert_title: Option<CFString>, + alert_prompt: Option<CFString>, + items: Option<&'a mut SecItems>, + keychain: Option<SecKeychain>, +} + +impl<'a> ImportOptions<'a> { + /// Creates a new builder with default options. + #[inline(always)] + #[must_use] + pub fn new() -> ImportOptions<'a> { + ImportOptions::default() + } + + /// Sets the filename from which the imported data came. + /// + /// The extension of the file will used as a hint for parsing. + #[inline] + pub fn filename(&mut self, filename: &str) -> &mut ImportOptions<'a> { + self.filename = Some(CFString::from_str(filename).unwrap()); + self + } + + /// Sets the passphrase to be used to decrypt the imported data. + #[inline] + pub fn passphrase(&mut self, passphrase: &str) -> &mut ImportOptions<'a> { + self.passphrase = Some(CFString::from_str(passphrase).unwrap().into_CFType()); + self + } + + /// Sets the passphrase to be used to decrypt the imported data. + #[inline] + pub fn passphrase_bytes(&mut self, passphrase: &[u8]) -> &mut ImportOptions<'a> { + self.passphrase = Some(CFData::from_buffer(passphrase).into_CFType()); + self + } + + /// If set, the user will be prompted to imput the passphrase used to + /// decrypt the imported data. + #[inline(always)] + pub fn secure_passphrase(&mut self, secure_passphrase: bool) -> &mut ImportOptions<'a> { + self.secure_passphrase = secure_passphrase; + self + } + + /// If set, imported items will have no access controls imposed on them. + #[inline(always)] + pub fn no_access_control(&mut self, no_access_control: bool) -> &mut ImportOptions<'a> { + self.no_access_control = no_access_control; + self + } + + /// Sets the title of the alert popup used with the `secure_passphrase` + /// option. + #[inline] + pub fn alert_title(&mut self, alert_title: &str) -> &mut ImportOptions<'a> { + self.alert_title = Some(CFString::from_str(alert_title).unwrap()); + self + } + + /// Sets the prompt of the alert popup used with the `secure_passphrase` + /// option. + #[inline] + pub fn alert_prompt(&mut self, alert_prompt: &str) -> &mut ImportOptions<'a> { + self.alert_prompt = Some(CFString::from_str(alert_prompt).unwrap()); + self + } + + /// Sets the object into which imported items will be placed. + #[inline(always)] + pub fn items(&mut self, items: &'a mut SecItems) -> &mut ImportOptions<'a> { + self.items = Some(items); + self + } + + /// Sets the keychain into which items will be imported. + /// + /// This must be specified to import `SecIdentity`s. + #[inline] + pub fn keychain(&mut self, keychain: &SecKeychain) -> &mut ImportOptions<'a> { + self.keychain = Some(keychain.clone()); + self + } + + /// Imports items from serialized data. + pub fn import(&mut self, data: &[u8]) -> Result<()> { + let data = CFData::from_buffer(data); + let data = data.as_concrete_TypeRef(); + + let filename = match self.filename { + Some(ref filename) => filename.as_concrete_TypeRef(), + None => ptr::null(), + }; + + let mut key_params = SecItemImportExportKeyParameters { + version: SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION, + flags: 0, + passphrase: ptr::null(), + alertTitle: ptr::null(), + alertPrompt: ptr::null(), + accessRef: ptr::null_mut(), + keyUsage: ptr::null_mut(), + keyAttributes: ptr::null(), + }; + + if let Some(ref passphrase) = self.passphrase { + key_params.passphrase = passphrase.as_CFTypeRef(); + } + + if self.secure_passphrase { + key_params.flags |= kSecKeySecurePassphrase; + } + + if self.no_access_control { + key_params.flags |= kSecKeyNoAccessControl; + } + + if let Some(ref alert_title) = self.alert_title { + key_params.alertTitle = alert_title.as_concrete_TypeRef(); + } + + if let Some(ref alert_prompt) = self.alert_prompt { + key_params.alertPrompt = alert_prompt.as_concrete_TypeRef(); + } + + let keychain = match self.keychain { + Some(ref keychain) => keychain.as_concrete_TypeRef(), + None => ptr::null_mut(), + }; + + let mut raw_items = ptr::null(); + let items_ref = match self.items { + Some(_) => std::ptr::addr_of_mut!(raw_items), + None => ptr::null_mut(), + }; + + unsafe { + let ret = SecItemImport( + data, + filename, + ptr::null_mut(), + ptr::null_mut(), + 0, + &key_params, + keychain, + items_ref, + ); + if ret != errSecSuccess { + return Err(Error::from_code(ret)); + } + + if let Some(ref mut items) = self.items { + let raw_items = CFArray::<CFType>::wrap_under_create_rule(raw_items); + for item in raw_items.iter() { + let type_id = item.type_of(); + if type_id == SecCertificate::type_id() { + items.certificates.push(SecCertificate::wrap_under_get_rule( + item.as_CFTypeRef() as *mut _, + )); + } else if type_id == SecIdentity::type_id() { + items.identities.push(SecIdentity::wrap_under_get_rule( + item.as_CFTypeRef() as *mut _, + )); + } else if type_id == SecKey::type_id() { + items + .keys + .push(SecKey::wrap_under_get_rule(item.as_CFTypeRef() as *mut _)); + } else { + panic!("Got bad type from SecItemImport: {type_id}"); + } + } + } + } + + Ok(()) + } +} + +/// A type which holds items imported from serialized data. +/// +/// Pass a reference to `ImportOptions::items`. +#[derive(Default)] +pub struct SecItems { + /// Imported certificates. + pub certificates: Vec<SecCertificate>, + /// Imported identities. + pub identities: Vec<SecIdentity>, + /// Imported keys. + pub keys: Vec<SecKey>, +} + +#[cfg(test)] +mod test { + use super::*; + use crate::import_export::*; + use crate::os::macos::keychain; + use tempfile::tempdir; + + #[test] + fn certificate() { + let data = include_bytes!("../../../test/server.der"); + let mut items = SecItems::default(); + ImportOptions::new() + .filename("server.der") + .items(&mut items) + .import(data) + .unwrap(); + assert_eq!(1, items.certificates.len()); + assert_eq!(0, items.identities.len()); + assert_eq!(0, items.keys.len()); + } + + #[test] + fn key() { + let data = include_bytes!("../../../test/server.key"); + let mut items = SecItems::default(); + ImportOptions::new() + .filename("server.key") + .items(&mut items) + .import(data) + .unwrap(); + assert_eq!(0, items.certificates.len()); + assert_eq!(0, items.identities.len()); + assert_eq!(1, items.keys.len()); + } + + #[test] + fn identity() { + let dir = tempdir().unwrap(); + let keychain = keychain::CreateOptions::new() + .password("password") + .create(dir.path().join("identity.keychain")) + .unwrap(); + + let data = include_bytes!("../../../test/server.p12"); + let mut items = SecItems::default(); + ImportOptions::new() + .filename("server.p12") + .passphrase("password123") + .items(&mut items) + .keychain(&keychain) + .import(data) + .unwrap(); + assert_eq!(1, items.identities.len()); + assert_eq!(0, items.certificates.len()); + assert_eq!(0, items.keys.len()); + } + + #[test] + #[ignore] // since it requires manual intervention + fn secure_passphrase_identity() { + let dir = tempdir().unwrap(); + let keychain = keychain::CreateOptions::new() + .password("password") + .create(dir.path().join("identity.keychain")) + .unwrap(); + + let data = include_bytes!("../../../test/server.p12"); + let mut items = SecItems::default(); + ImportOptions::new() + .filename("server.p12") + .secure_passphrase(true) + .alert_title("alert title") + .alert_prompt("alert prompt") + .items(&mut items) + .keychain(&keychain) + .import(data) + .unwrap(); + assert_eq!(1, items.identities.len()); + assert_eq!(0, items.certificates.len()); + assert_eq!(0, items.keys.len()); + } + + #[test] + fn pkcs12_import() { + use super::Pkcs12ImportOptionsExt; + + let dir = tempdir().unwrap(); + let keychain = keychain::CreateOptions::new() + .password("password") + .create(dir.path().join("pkcs12_import")) + .unwrap(); + + let data = include_bytes!("../../../test/server.p12"); + let identities = p!(Pkcs12ImportOptions::new() + .passphrase("password123") + .keychain(keychain) + .import(data)); + assert_eq!(1, identities.len()); + assert_eq!( + hex::encode(identities[0].key_id.as_ref().unwrap()), + "ed6492936dcc8907e397e573b36e633458dc33f1" + ); + } +} diff --git a/vendor/security-framework/src/os/macos/item.rs b/vendor/security-framework/src/os/macos/item.rs new file mode 100644 index 00000000..18a4d2e8 --- /dev/null +++ b/vendor/security-framework/src/os/macos/item.rs @@ -0,0 +1,47 @@ +//! OSX specific functionality for items. +use crate::item::ItemSearchOptions; +use crate::os::macos::keychain::SecKeychain; +use crate::ItemSearchOptionsInternals; + +// Moved to crate::Key +pub use crate::key::KeyType; + +/// An extension trait adding OSX specific functionality to `ItemSearchOptions`. +pub trait ItemSearchOptionsExt { + /// Search within the specified keychains. + /// + /// If this is not called, the default keychain will be searched. + fn keychains(&mut self, keychains: &[SecKeychain]) -> &mut Self; +} + +impl ItemSearchOptionsExt for ItemSearchOptions { + #[inline(always)] + fn keychains(&mut self, keychains: &[SecKeychain]) -> &mut Self { + ItemSearchOptionsInternals::keychains(self, keychains) + } +} + +#[cfg(test)] +mod test { + use crate::item::*; + use crate::os::macos::certificate::SecCertificateExt; + use crate::os::macos::item::ItemSearchOptionsExt; + use crate::os::macos::test::keychain; + use tempfile::tempdir; + + #[test] + fn find_certificate() { + let dir = p!(tempdir()); + let keychain = keychain(dir.path()); + let results = p!(ItemSearchOptions::new() + .keychains(&[keychain]) + .class(ItemClass::certificate()) + .search()); + assert_eq!(1, results.len()); + let certificate = match results[0] { + SearchResult::Ref(Reference::Certificate(ref cert)) => cert, + _ => panic!("expected certificate"), + }; + assert_eq!("foobar.com", p!(certificate.common_name())); + } +} diff --git a/vendor/security-framework/src/os/macos/key.rs b/vendor/security-framework/src/os/macos/key.rs new file mode 100644 index 00000000..f6a20e93 --- /dev/null +++ b/vendor/security-framework/src/os/macos/key.rs @@ -0,0 +1,38 @@ +//! OSX specific functionality for keys. +use core_foundation::base::TCFType; +use core_foundation::data::CFData; +use core_foundation::dictionary::CFDictionary; +use core_foundation::error::CFError; +use core_foundation::string::CFString; +use security_framework_sys::item::kSecAttrKeyType; +use security_framework_sys::key::SecKeyCreateFromData; +use std::ptr; + +use crate::key::{KeyType, SecKey}; + +/// An extension trait adding OSX specific functionality to `SecKey`. +pub trait SecKeyExt { + /// Creates a new `SecKey` from a buffer containing key data. + fn from_data(key_type: KeyType, key_data: &CFData) -> Result<SecKey, CFError>; +} + +impl SecKeyExt for SecKey { + fn from_data(key_type: KeyType, key_data: &CFData) -> Result<Self, CFError> { + unsafe { + let key = CFString::wrap_under_get_rule(kSecAttrKeyType); + let dict = CFDictionary::from_CFType_pairs(&[(key, key_type.to_str())]); + + let mut err = ptr::null_mut(); + let key = SecKeyCreateFromData( + dict.as_concrete_TypeRef(), + key_data.as_concrete_TypeRef(), + &mut err, + ); + if key.is_null() { + Err(CFError::wrap_under_create_rule(err)) + } else { + Ok(Self::wrap_under_create_rule(key)) + } + } + } +} diff --git a/vendor/security-framework/src/os/macos/keychain.rs b/vendor/security-framework/src/os/macos/keychain.rs new file mode 100644 index 00000000..bce5e00a --- /dev/null +++ b/vendor/security-framework/src/os/macos/keychain.rs @@ -0,0 +1,281 @@ +//! Keychain support. + +use core_foundation::base::{Boolean, TCFType}; +use security_framework_sys::base::{errSecSuccess, SecKeychainRef}; +use security_framework_sys::keychain::*; +use std::ffi::CString; +use std::os::raw::c_void; +use std::os::unix::ffi::OsStrExt; +use std::path::Path; +use std::ptr; + +use crate::base::{Error, Result}; +use crate::cvt; +use crate::os::macos::access::SecAccess; + +pub use security_framework_sys::keychain::SecPreferencesDomain; + +declare_TCFType! { + /// A type representing a keychain. + SecKeychain, SecKeychainRef +} +impl_TCFType!(SecKeychain, SecKeychainRef, SecKeychainGetTypeID); + +unsafe impl Sync for SecKeychain {} +unsafe impl Send for SecKeychain {} + +impl SecKeychain { + /// Creates a `SecKeychain` object corresponding to the user's default + /// keychain. + #[inline] + #[allow(clippy::should_implement_trait)] + pub fn default() -> Result<Self> { + unsafe { + let mut keychain = ptr::null_mut(); + cvt(SecKeychainCopyDefault(&mut keychain))?; + Ok(Self::wrap_under_create_rule(keychain)) + } + } + + /// Creates a `SecKeychain` object corresponding to the user's default + /// keychain for the given domain. + pub fn default_for_domain(domain: SecPreferencesDomain) -> Result<Self> { + unsafe { + let mut keychain = ptr::null_mut(); + cvt(SecKeychainCopyDomainDefault(domain, &mut keychain))?; + Ok(Self::wrap_under_create_rule(keychain)) + } + } + + /// Opens a keychain from a file. + pub fn open<P: AsRef<Path>>(path: P) -> Result<Self> { + let path_name = [ + path.as_ref().as_os_str().as_bytes(), + std::slice::from_ref(&0) + ].concat(); + + unsafe { + let mut keychain = ptr::null_mut(); + cvt(SecKeychainOpen(path_name.as_ptr().cast(), &mut keychain))?; + Ok(Self::wrap_under_create_rule(keychain)) + } + } + + /// Unlocks the keychain. + /// + /// If a password is not specified, the user will be prompted to enter it. + pub fn unlock(&mut self, password: Option<&str>) -> Result<()> { + let (len, ptr, use_password) = match password { + Some(password) => (password.len(), password.as_ptr().cast(), true), + None => (0, ptr::null(), false), + }; + + unsafe { + cvt(SecKeychainUnlock( + self.as_concrete_TypeRef(), + len as u32, + ptr, + Boolean::from(use_password), + )) + } + } + + /// Sets settings of the keychain. + #[inline] + pub fn set_settings(&mut self, settings: &KeychainSettings) -> Result<()> { + unsafe { + cvt(SecKeychainSetSettings( + self.as_concrete_TypeRef(), + &settings.0, + )) + } + } + + #[cfg(target_os = "macos")] + /// Disables the user interface for keychain services functions that + /// automatically display a user interface. + pub fn disable_user_interaction() -> Result<KeychainUserInteractionLock> { + let code = unsafe { SecKeychainSetUserInteractionAllowed(0u8) }; + + if code != errSecSuccess { + Err(Error::from_code(code)) + } else { + Ok(KeychainUserInteractionLock) + } + } + + #[cfg(target_os = "macos")] + /// Indicates whether keychain services functions that normally display a + /// user interaction are allowed to do so. + pub fn user_interaction_allowed() -> Result<bool> { + let mut state: Boolean = 0; + let code = unsafe { SecKeychainGetUserInteractionAllowed(&mut state) }; + + if code != errSecSuccess { + Err(Error::from_code(code)) + } else { + Ok(state != 0) + } + } +} + +/// A builder type to create new keychains. +#[derive(Default)] +pub struct CreateOptions { + password: Option<String>, + prompt_user: bool, + access: Option<SecAccess>, +} + +impl CreateOptions { + /// Creates a new builder with default options. + #[inline(always)] + #[must_use] + pub fn new() -> Self { + Self::default() + } + + /// Sets the password to be used to protect the keychain. + #[inline] + pub fn password(&mut self, password: &str) -> &mut Self { + self.password = Some(password.into()); + self + } + + /// If set, the user will be prompted to provide a password used to + /// protect the keychain. + #[inline(always)] + pub fn prompt_user(&mut self, prompt_user: bool) -> &mut Self { + self.prompt_user = prompt_user; + self + } + + /// Sets the access control applied to the keychain. + #[inline(always)] + pub fn access(&mut self, access: SecAccess) -> &mut Self { + self.access = Some(access); + self + } + + /// Creates a new keychain at the specified location on the filesystem. + pub fn create<P: AsRef<Path>>(&self, path: P) -> Result<SecKeychain> { + unsafe { + let path_name = path.as_ref().as_os_str().as_bytes(); + // FIXME + let path_name = CString::new(path_name).unwrap(); + + let (password, password_len) = match self.password { + Some(ref password) => (password.as_ptr().cast::<c_void>(), password.len() as u32), + None => (ptr::null(), 0), + }; + + let access = match self.access { + Some(ref access) => access.as_concrete_TypeRef(), + None => ptr::null_mut(), + }; + + let mut keychain = ptr::null_mut(); + cvt(SecKeychainCreate( + path_name.as_ptr(), + password_len, + password, + Boolean::from(self.prompt_user), + access, + &mut keychain, + ))?; + + Ok(SecKeychain::wrap_under_create_rule(keychain)) + } + } +} + +/// Settings associated with a `SecKeychain`. +pub struct KeychainSettings(SecKeychainSettings); + +impl KeychainSettings { + /// Creates a new `KeychainSettings` with default settings. + #[inline] + #[must_use] + pub fn new() -> Self { + Self(SecKeychainSettings { + version: SEC_KEYCHAIN_SETTINGS_VERS1, + lockOnSleep: 0, + useLockInterval: 0, + lockInterval: i32::max_value() as u32, + }) + } + + /// If set, the keychain will automatically lock when the computer sleeps. + /// + /// Defaults to `false`. + #[inline(always)] + pub fn set_lock_on_sleep(&mut self, lock_on_sleep: bool) { + self.0.lockOnSleep = Boolean::from(lock_on_sleep); + } + + /// Sets the interval of time in seconds after which the keychain is + /// automatically locked. + /// + /// Defaults to `None`. + pub fn set_lock_interval(&mut self, lock_interval: Option<u32>) { + match lock_interval { + Some(lock_interval) => { + self.0.useLockInterval = 1; + self.0.lockInterval = lock_interval; + } + None => { + self.0.useLockInterval = 0; + self.0.lockInterval = i32::max_value() as u32; + } + } + } +} + +impl Default for KeychainSettings { + #[inline(always)] + fn default() -> Self { + Self::new() + } +} + +#[cfg(target_os = "macos")] +#[must_use = "The user interaction is disabled for the lifetime of the returned object"] +/// Automatically re-enables user interaction. +pub struct KeychainUserInteractionLock; + +#[cfg(target_os = "macos")] +impl Drop for KeychainUserInteractionLock { + #[inline(always)] + fn drop(&mut self) { + unsafe { SecKeychainSetUserInteractionAllowed(1u8) }; + } +} + +#[cfg(test)] +mod test { + use tempfile::tempdir; + + use super::*; + + #[test] + fn create_options() { + let dir = tempdir().unwrap(); + + let mut keychain = CreateOptions::new() + .password("foobar") + .create(dir.path().join("test.keychain")) + .unwrap(); + + keychain.set_settings(&KeychainSettings::new()).unwrap(); + } + + #[test] + fn disable_user_interaction() { + assert!(SecKeychain::user_interaction_allowed().unwrap()); + { + let _lock = SecKeychain::disable_user_interaction().unwrap(); + assert!(!SecKeychain::user_interaction_allowed().unwrap()); + } + assert!(SecKeychain::user_interaction_allowed().unwrap()); + } +} diff --git a/vendor/security-framework/src/os/macos/keychain_item.rs b/vendor/security-framework/src/os/macos/keychain_item.rs new file mode 100644 index 00000000..fd7b452a --- /dev/null +++ b/vendor/security-framework/src/os/macos/keychain_item.rs @@ -0,0 +1,26 @@ +//! Keychain item support. + +use core_foundation::base::TCFType; +use security_framework_sys::base::SecKeychainItemRef; +use security_framework_sys::keychain_item::SecKeychainItemGetTypeID; +use std::fmt; + +declare_TCFType! { + /// A type representing a keychain item. + SecKeychainItem, SecKeychainItemRef +} +impl_TCFType!( + SecKeychainItem, + SecKeychainItemRef, + SecKeychainItemGetTypeID +); + +unsafe impl Sync for SecKeychainItem {} +unsafe impl Send for SecKeychainItem {} + +impl fmt::Debug for SecKeychainItem { + #[cold] + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt.debug_struct("SecKeychainItem").finish_non_exhaustive() + } +} diff --git a/vendor/security-framework/src/os/macos/mod.rs b/vendor/security-framework/src/os/macos/mod.rs new file mode 100644 index 00000000..3e468fc3 --- /dev/null +++ b/vendor/security-framework/src/os/macos/mod.rs @@ -0,0 +1,52 @@ +//! OSX specific extensions. + +pub mod access; +pub mod certificate; +pub mod certificate_oids; +pub mod code_signing; +pub mod digest_transform; +pub mod encrypt_transform; +pub mod identity; +pub mod import_export; +pub mod item; +pub mod key; +pub mod keychain; +pub mod keychain_item; +pub mod passwords; +pub mod secure_transport; +pub mod transform; + +#[cfg(test)] +pub mod test { + use crate::identity::SecIdentity; + use crate::item::{ItemClass, ItemSearchOptions, Reference, SearchResult}; + use crate::os::macos::item::ItemSearchOptionsExt; + use crate::os::macos::keychain::SecKeychain; + use std::fs::File; + use std::io::prelude::*; + use std::path::Path; + + #[must_use] pub fn identity(dir: &Path) -> SecIdentity { + // FIXME https://github.com/rust-lang/rust/issues/30018 + let keychain = keychain(dir); + let mut items = p!(ItemSearchOptions::new() + .class(ItemClass::identity()) + .keychains(&[keychain]) + .search()); + match items.pop().unwrap() { + SearchResult::Ref(Reference::Identity(identity)) => identity, + _ => panic!("expected identity"), + } + } + + #[must_use] pub fn keychain(dir: &Path) -> SecKeychain { + let path = dir.join("server.keychain"); + let mut file = p!(File::create(&path)); + p!(file.write_all(include_bytes!("../../../test/server.keychain"))); + drop(file); + + let mut keychain = p!(SecKeychain::open(&path)); + p!(keychain.unlock(Some("password123"))); + keychain + } +} diff --git a/vendor/security-framework/src/os/macos/passwords.rs b/vendor/security-framework/src/os/macos/passwords.rs new file mode 100644 index 00000000..8587a3c5 --- /dev/null +++ b/vendor/security-framework/src/os/macos/passwords.rs @@ -0,0 +1,525 @@ +//! Password support. + +use crate::os::macos::keychain::SecKeychain; +use crate::os::macos::keychain_item::SecKeychainItem; +use core_foundation::array::CFArray; +use core_foundation::base::TCFType; +pub use security_framework_sys::keychain::{SecAuthenticationType, SecProtocolType}; +use security_framework_sys::keychain::{ + SecKeychainAddGenericPassword, SecKeychainAddInternetPassword, SecKeychainFindGenericPassword, + SecKeychainFindInternetPassword, +}; +use security_framework_sys::keychain_item::{ + SecKeychainItemDelete, SecKeychainItemFreeContent, SecKeychainItemModifyAttributesAndData, +}; +use std::fmt; +use std::fmt::Write; +use std::ops::Deref; +use std::ptr; +use std::slice; + +use crate::base::Result; +use crate::cvt; + +/// Password slice. Use `.as_ref()` to get `&[u8]` or `.to_owned()` to get `Vec<u8>` +pub struct SecKeychainItemPassword { + data: *const u8, + data_len: usize, +} + +impl fmt::Debug for SecKeychainItemPassword { + #[cold] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + for _ in 0..self.data_len { + f.write_char('•')?; + } + Ok(()) + } +} + +impl AsRef<[u8]> for SecKeychainItemPassword { + #[inline] + fn as_ref(&self) -> &[u8] { + unsafe { slice::from_raw_parts(self.data, self.data_len) } + } +} + +impl Deref for SecKeychainItemPassword { + type Target = [u8]; + #[inline(always)] + fn deref(&self) -> &Self::Target { + self.as_ref() + } +} + +impl Drop for SecKeychainItemPassword { + #[inline] + fn drop(&mut self) { + unsafe { + SecKeychainItemFreeContent(ptr::null_mut(), self.data as *mut _); + } + } +} + +impl SecKeychainItem { + /// Modify keychain item in-place, replacing its password with the given one + pub fn set_password(&mut self, password: &[u8]) -> Result<()> { + unsafe { + cvt(SecKeychainItemModifyAttributesAndData( + self.as_CFTypeRef() as *mut _, + ptr::null(), + password.len() as u32, + password.as_ptr().cast(), + ))?; + } + Ok(()) + } + + /// Delete this item from its keychain + #[inline] + pub fn delete(self) { + unsafe { + SecKeychainItemDelete(self.as_CFTypeRef() as *mut _); + } + } +} + +/// Find a generic password. +/// +/// The underlying system supports passwords with 0 values, so this +/// returns a vector of bytes rather than a string. +/// +/// * `keychains` is an array of keychains to search or None to search +/// the default keychain. +/// * `service` is the name of the service to search for. +/// * `account` is the name of the account to search for. +pub fn find_generic_password( + keychains: Option<&[SecKeychain]>, + service: &str, + account: &str, +) -> Result<(SecKeychainItemPassword, SecKeychainItem)> { + let keychains_or_none = keychains.map(CFArray::from_CFTypes); + + let keychains_or_null = match keychains_or_none { + None => ptr::null(), + Some(ref keychains) => keychains.as_CFTypeRef(), + }; + + let mut data_len = 0; + let mut data = ptr::null_mut(); + let mut item = ptr::null_mut(); + + unsafe { + cvt(SecKeychainFindGenericPassword( + keychains_or_null, + service.len() as u32, + service.as_ptr().cast(), + account.len() as u32, + account.as_ptr().cast(), + &mut data_len, + &mut data, + &mut item, + ))?; + Ok(( + SecKeychainItemPassword { + data: data as *const _, + data_len: data_len as usize, + }, + SecKeychainItem::wrap_under_create_rule(item), + )) + } +} + +/// * `keychains` is an array of keychains to search or None to search +/// the default keychain. +/// * `server`: server name. +/// * `security_domain`: security domain. This parameter is optional. +/// * `account`: account name. +/// * `path`: the path. +/// * `port`: The TCP/IP port number. +/// * `protocol`: The protocol associated with this password. +/// * `authentication_type`: The authentication scheme used. +#[allow(clippy::too_many_arguments)] +pub fn find_internet_password( + keychains: Option<&[SecKeychain]>, + server: &str, + security_domain: Option<&str>, + account: &str, + path: &str, + port: Option<u16>, + protocol: SecProtocolType, + authentication_type: SecAuthenticationType, +) -> Result<(SecKeychainItemPassword, SecKeychainItem)> { + let keychains_or_none = keychains.map(CFArray::from_CFTypes); + + let keychains_or_null = match keychains_or_none { + None => ptr::null(), + Some(ref keychains) => keychains.as_CFTypeRef(), + }; + + let mut data_len = 0; + let mut data = ptr::null_mut(); + let mut item = ptr::null_mut(); + + unsafe { + cvt(SecKeychainFindInternetPassword( + keychains_or_null, + server.len() as u32, + server.as_ptr().cast(), + security_domain.map_or(0, |s| s.len() as u32), + security_domain + .map_or(ptr::null(), |s| s.as_ptr().cast()), + account.len() as u32, + account.as_ptr().cast(), + path.len() as u32, + path.as_ptr().cast(), + port.unwrap_or(0), + protocol, + authentication_type, + &mut data_len, + &mut data, + &mut item, + ))?; + Ok(( + SecKeychainItemPassword { + data: data as *const _, + data_len: data_len as usize, + }, + SecKeychainItem::wrap_under_create_rule(item), + )) + } +} + +impl SecKeychain { + /// Find application password in this keychain + #[inline] + pub fn find_generic_password( + &self, + service: &str, + account: &str, + ) -> Result<(SecKeychainItemPassword, SecKeychainItem)> { + find_generic_password(Some(&[self.clone()]), service, account) + } + + /// Find internet password in this keychain + #[inline] + #[allow(clippy::too_many_arguments)] + pub fn find_internet_password( + &self, + server: &str, + security_domain: Option<&str>, + account: &str, + path: &str, + port: Option<u16>, + protocol: SecProtocolType, + authentication_type: SecAuthenticationType, + ) -> Result<(SecKeychainItemPassword, SecKeychainItem)> { + find_internet_password( + Some(&[self.clone()]), + server, + security_domain, + account, + path, + port, + protocol, + authentication_type, + ) + } + + /// Update existing or add new internet password + #[allow(clippy::too_many_arguments)] + pub fn set_internet_password( + &self, + server: &str, + security_domain: Option<&str>, + account: &str, + path: &str, + port: Option<u16>, + protocol: SecProtocolType, + authentication_type: SecAuthenticationType, + password: &[u8], + ) -> Result<()> { + match self.find_internet_password( + server, + security_domain, + account, + path, + port, + protocol, + authentication_type, + ) { + Ok((_, mut item)) => item.set_password(password), + _ => self.add_internet_password( + server, + security_domain, + account, + path, + port, + protocol, + authentication_type, + password, + ), + } + } + + /// Set a generic password. + /// + /// * `keychain_opt` is the keychain to use or None to use the default + /// keychain. + /// * `service` is the associated service name for the password. + /// * `account` is the associated account name for the password. + /// * `password` is the password itself. + pub fn set_generic_password( + &self, + service: &str, + account: &str, + password: &[u8], + ) -> Result<()> { + match self.find_generic_password(service, account) { + Ok((_, mut item)) => item.set_password(password), + _ => self.add_generic_password(service, account, password), + } + } + + /// Add application password to the keychain, without checking if it exists already + /// + /// See `set_generic_password()` + #[inline] + pub fn add_generic_password( + &self, + service: &str, + account: &str, + password: &[u8], + ) -> Result<()> { + unsafe { + cvt(SecKeychainAddGenericPassword( + self.as_CFTypeRef() as *mut _, + service.len() as u32, + service.as_ptr().cast(), + account.len() as u32, + account.as_ptr().cast(), + password.len() as u32, + password.as_ptr().cast(), + ptr::null_mut(), + ))?; + } + Ok(()) + } + + /// Add internet password to the keychain, without checking if it exists already + /// + /// See `set_internet_password()` + #[inline] + #[allow(clippy::too_many_arguments)] + pub fn add_internet_password( + &self, + server: &str, + security_domain: Option<&str>, + account: &str, + path: &str, + port: Option<u16>, + protocol: SecProtocolType, + authentication_type: SecAuthenticationType, + password: &[u8], + ) -> Result<()> { + unsafe { + cvt(SecKeychainAddInternetPassword( + self.as_CFTypeRef() as *mut _, + server.len() as u32, + server.as_ptr().cast(), + security_domain.map_or(0, |s| s.len() as u32), + security_domain + .map_or(ptr::null(), |s| s.as_ptr().cast()), + account.len() as u32, + account.as_ptr().cast(), + path.len() as u32, + path.as_ptr().cast(), + port.unwrap_or(0), + protocol, + authentication_type, + password.len() as u32, + password.as_ptr().cast(), + ptr::null_mut(), + ))?; + } + Ok(()) + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::os::macos::keychain::CreateOptions; + use tempfile::tempdir; + use tempfile::TempDir; + + fn temp_keychain_setup(name: &str) -> (TempDir, SecKeychain) { + let dir = tempdir().expect("TempDir::new"); + let keychain = CreateOptions::new() + .password("foobar") + .create(dir.path().join(name.to_string() + ".keychain")) + .expect("create keychain"); + + (dir, keychain) + } + + fn temp_keychain_teardown(dir: TempDir) { + dir.close().expect("temp dir close"); + } + + #[test] + fn missing_password_temp() { + let (dir, keychain) = temp_keychain_setup("missing_password"); + let keychains = vec![keychain]; + + let service = "temp_this_service_does_not_exist"; + let account = "this_account_is_bogus"; + let found = find_generic_password(Some(&keychains), service, account); + + assert!(found.is_err()); + + temp_keychain_teardown(dir); + } + + #[test] + #[cfg(feature = "default_keychain_tests")] + fn missing_password_default() { + let service = "default_this_service_does_not_exist"; + let account = "this_account_is_bogus"; + let found = find_generic_password(None, service, account); + + assert!(found.is_err()); + } + + #[test] + fn round_trip_password_temp() { + let (dir, keychain) = temp_keychain_setup("round_trip_password"); + + let service = "test_round_trip_password_temp"; + let account = "temp_this_is_the_test_account"; + let password = String::from("deadbeef").into_bytes(); + + keychain + .set_generic_password(service, account, &password) + .expect("set_generic_password"); + let (found, item) = keychain + .find_generic_password(service, account) + .expect("find_generic_password"); + assert_eq!(found.to_owned(), password); + + item.delete(); + + temp_keychain_teardown(dir); + } + + #[test] + #[cfg(feature = "default_keychain_tests")] + fn round_trip_password_default() { + let service = "test_round_trip_password_default"; + let account = "this_is_the_test_account"; + let password = String::from("deadbeef").into_bytes(); + + SecKeychain::default() + .expect("default keychain") + .set_generic_password(service, account, &password) + .expect("set_generic_password"); + let (found, item) = + find_generic_password(None, service, account).expect("find_generic_password"); + assert_eq!(&*found, &password[..]); + + item.delete(); + } + + #[test] + fn change_password_temp() { + let (dir, keychain) = temp_keychain_setup("change_password"); + let keychains = vec![keychain]; + + let service = "test_change_password_temp"; + let account = "this_is_the_test_account"; + let pw1 = String::from("password1").into_bytes(); + let pw2 = String::from("password2").into_bytes(); + + keychains[0] + .set_generic_password(service, account, &pw1) + .expect("set_generic_password1"); + let (found, _) = find_generic_password(Some(&keychains), service, account) + .expect("find_generic_password1"); + assert_eq!(found.as_ref(), &pw1[..]); + + keychains[0] + .set_generic_password(service, account, &pw2) + .expect("set_generic_password2"); + let (found, item) = find_generic_password(Some(&keychains), service, account) + .expect("find_generic_password2"); + assert_eq!(&*found, &pw2[..]); + + item.delete(); + + temp_keychain_teardown(dir); + } + + #[test] + #[cfg(feature = "default_keychain_tests")] + fn change_password_default() { + let service = "test_change_password_default"; + let account = "this_is_the_test_account"; + let pw1 = String::from("password1").into_bytes(); + let pw2 = String::from("password2").into_bytes(); + + SecKeychain::default() + .expect("default keychain") + .set_generic_password(service, account, &pw1) + .expect("set_generic_password1"); + let (found, _) = + find_generic_password(None, service, account).expect("find_generic_password1"); + assert_eq!(found.to_owned(), pw1); + + SecKeychain::default() + .expect("default keychain") + .set_generic_password(service, account, &pw2) + .expect("set_generic_password2"); + let (found, item) = + find_generic_password(None, service, account).expect("find_generic_password2"); + assert_eq!(found.to_owned(), pw2); + + item.delete(); + } + + #[test] + fn cross_keychain_corruption_temp() { + let (dir1, keychain1) = temp_keychain_setup("cross_corrupt1"); + let (dir2, keychain2) = temp_keychain_setup("cross_corrupt2"); + let keychains1 = vec![keychain1.clone()]; + let keychains2 = vec![keychain2.clone()]; + let both_keychains = vec![keychain1, keychain2]; + + let service = "temp_this_service_does_not_exist"; + let account = "this_account_is_bogus"; + let password = String::from("deadbeef").into_bytes(); + + // Make sure this password doesn't exist in either keychain. + let found = find_generic_password(Some(&both_keychains), service, account); + assert!(found.is_err()); + + // Set a password in one keychain. + keychains1[0] + .set_generic_password(service, account, &password) + .expect("set_generic_password"); + + // Make sure it's found in that keychain. + let (found, item) = find_generic_password(Some(&keychains1), service, account) + .expect("find_generic_password1"); + assert_eq!(found.to_owned(), password); + + // Make sure it's _not_ found in the other keychain. + let found = find_generic_password(Some(&keychains2), service, account); + assert!(found.is_err()); + + // Cleanup. + item.delete(); + + temp_keychain_teardown(dir1); + temp_keychain_teardown(dir2); + } +} diff --git a/vendor/security-framework/src/os/macos/secure_transport.rs b/vendor/security-framework/src/os/macos/secure_transport.rs new file mode 100644 index 00000000..7d01f066 --- /dev/null +++ b/vendor/security-framework/src/os/macos/secure_transport.rs @@ -0,0 +1,647 @@ +//! OSX specific extensions to Secure Transport functionality. + +use core_foundation::array::CFArray; +use core_foundation::base::TCFType; +use security_framework_sys::secure_transport::*; +use std::ptr; +use std::slice; + +use crate::base::Result; +use crate::certificate::SecCertificate; +use crate::secure_transport::{MidHandshakeSslStream, SslContext}; +use crate::{cvt, AsInner}; + +/// An extension trait adding OSX specific functionality to the `SslContext` +/// type. +pub trait SslContextExt { + /// Returns the DER encoded data specifying the parameters used for + /// Diffie-Hellman key exchange. + fn diffie_hellman_params(&self) -> Result<Option<&[u8]>>; + + /// Sets the parameters used for Diffie-Hellman key exchange, in the + /// DER format used by OpenSSL. + /// + /// If a cipher suite which uses Diffie-Hellman key exchange is selected, + /// parameters will automatically be generated if none are provided with + /// this method, but this process can take up to 30 seconds. + /// + /// This can only be called on server-side sessions. + fn set_diffie_hellman_params(&mut self, dh_params: &[u8]) -> Result<()>; + + /// Returns the certificate authorities used to validate client + /// certificates. + fn certificate_authorities(&self) -> Result<Option<Vec<SecCertificate>>>; + + /// Sets the certificate authorities used to validate client certificates, + /// replacing any that are already present. + fn set_certificate_authorities(&mut self, certs: &[SecCertificate]) -> Result<()>; + + /// Adds certificate authorities used to validate client certificates. + fn add_certificate_authorities(&mut self, certs: &[SecCertificate]) -> Result<()>; + + /// If enabled, server identity changes are allowed during renegotiation. + /// + /// It is disabled by default to protect against triple handshake attacks. + /// + /// Requires the `OSX_10_11` (or greater) feature. + #[cfg(feature = "OSX_10_11")] + fn allow_server_identity_change(&self) -> Result<bool>; + + /// If enabled, server identity changes are allowed during renegotiation. + /// + /// It is disabled by default to protect against triple handshake attacks. + /// + /// Requires the `OSX_10_11` (or greater) feature. + #[cfg(feature = "OSX_10_11")] + fn set_allow_server_identity_change(&mut self, value: bool) -> Result<()>; + + /// If enabled, fallback countermeasures will be used during negotiation. + /// + /// It should be enabled when renegotiating with a peer with a lower + /// maximum protocol version due to an earlier failure to connect. + /// + /// Requires the `OSX_10_10` (or greater) feature. + #[cfg(feature = "OSX_10_10")] + fn fallback(&self) -> Result<bool>; + + /// If enabled, fallback countermeasures will be used during negotiation. + /// + /// It should be enabled when renegotiating with a peer with a lower + /// maximum protocol version due to an earlier failure to connect. + /// + /// Requires the `OSX_10_10` (or greater) feature. + #[cfg(feature = "OSX_10_10")] + fn set_fallback(&mut self, value: bool) -> Result<()>; + + /// If enabled, the handshake process will pause and return when the client + /// hello is recieved to support server name identification. + /// + /// Requires the `OSX_10_11` (or greater) feature. + #[cfg(feature = "OSX_10_11")] + fn break_on_client_hello(&self) -> Result<bool>; + + /// If enabled, the handshake process will pause and return when the client + /// hello is recieved to support server name identification. + /// + /// Requires the `OSX_10_11` (or greater) feature. + #[cfg(feature = "OSX_10_11")] + fn set_break_on_client_hello(&mut self, value: bool) -> Result<()>; +} + +macro_rules! impl_options { + ($($(#[$a:meta])* const $opt:ident: $get:ident & $set:ident,)*) => { + $( + $(#[$a])* + #[inline] + fn $set(&mut self, value: bool) -> Result<()> { + unsafe { + cvt(SSLSetSessionOption(self.as_inner(), + $opt, + value as ::core_foundation::base::Boolean)) + } + } + + $(#[$a])* + #[inline] + fn $get(&self) -> Result<bool> { + let mut value = 0; + unsafe { cvt(SSLGetSessionOption(self.as_inner(), $opt, &mut value))?; } + Ok(value != 0) + } + )* + } +} + +impl SslContextExt for SslContext { + fn diffie_hellman_params(&self) -> Result<Option<&[u8]>> { + unsafe { + let mut ptr = ptr::null(); + let mut len = 0; + cvt(SSLGetDiffieHellmanParams( + self.as_inner(), + &mut ptr, + &mut len, + ))?; + if ptr.is_null() { + Ok(None) + } else { + Ok(Some(slice::from_raw_parts(ptr.cast::<u8>(), len))) + } + } + } + + fn set_diffie_hellman_params(&mut self, dh_params: &[u8]) -> Result<()> { + unsafe { + cvt(SSLSetDiffieHellmanParams( + self.as_inner(), + dh_params.as_ptr().cast(), + dh_params.len(), + )) + } + } + + fn certificate_authorities(&self) -> Result<Option<Vec<SecCertificate>>> { + unsafe { + let mut raw_certs = ptr::null(); + cvt(SSLCopyCertificateAuthorities( + self.as_inner(), + &mut raw_certs, + ))?; + if raw_certs.is_null() { + Ok(None) + } else { + let certs = CFArray::<SecCertificate>::wrap_under_create_rule(raw_certs) + .iter() + .map(|c| c.clone()) + .collect(); + Ok(Some(certs)) + } + } + } + + fn set_certificate_authorities(&mut self, certs: &[SecCertificate]) -> Result<()> { + unsafe { + let certs = CFArray::from_CFTypes(certs); + cvt(SSLSetCertificateAuthorities( + self.as_inner(), + certs.as_CFTypeRef(), + 1, + )) + } + } + + fn add_certificate_authorities(&mut self, certs: &[SecCertificate]) -> Result<()> { + unsafe { + let certs = CFArray::from_CFTypes(certs); + cvt(SSLSetCertificateAuthorities( + self.as_inner(), + certs.as_CFTypeRef(), + 0, + )) + } + } + + impl_options! { + #[cfg(feature = "OSX_10_11")] + const kSSLSessionOptionAllowServerIdentityChange: allow_server_identity_change & set_allow_server_identity_change, + #[cfg(feature = "OSX_10_10")] + const kSSLSessionOptionFallback: fallback & set_fallback, + #[cfg(feature = "OSX_10_11")] + const kSSLSessionOptionBreakOnClientHello: break_on_client_hello & set_break_on_client_hello, + } +} + +/// An extension trait adding OSX specific functionality to the +/// `MidHandshakeSslStream` type. +pub trait MidHandshakeSslStreamExt { + /// Returns `true` iff `break_on_client_hello` was set and the handshake + /// has progressed to that point. + /// + /// Requires the `OSX_10_11` (or greater) feature. + #[cfg(feature = "OSX_10_11")] + fn client_hello_received(&self) -> bool; +} + +impl<S> MidHandshakeSslStreamExt for MidHandshakeSslStream<S> { + #[cfg(feature = "OSX_10_11")] + fn client_hello_received(&self) -> bool { + self.error().code() == errSSLClientHelloReceived + } +} + +#[cfg(test)] +mod test { + use std::io::prelude::*; + use std::net::{TcpListener, TcpStream}; + use std::thread; + use tempfile::tempdir; + + use super::*; + use crate::cipher_suite::CipherSuite; + use crate::os::macos::test::identity; + use crate::secure_transport::*; + use crate::test::certificate; + + #[test] + fn server_client() { + let listener = p!(TcpListener::bind("localhost:0")); + let port = p!(listener.local_addr()).port(); + + let handle = thread::spawn(move || { + let dir = p!(tempdir()); + + let mut ctx = p!(SslContext::new( + SslProtocolSide::SERVER, + SslConnectionType::STREAM + )); + let identity = identity(dir.path()); + p!(ctx.set_certificate(&identity, &[])); + + let stream = p!(listener.accept()).0; + let mut stream = p!(ctx.handshake(stream)); + + let mut buf = [0; 12]; + p!(stream.read(&mut buf)); + assert_eq!(&buf[..], b"hello world!"); + }); + + let mut ctx = p!(SslContext::new( + SslProtocolSide::CLIENT, + SslConnectionType::STREAM + )); + p!(ctx.set_break_on_server_auth(true)); + let stream = p!(TcpStream::connect(("localhost", port))); + + let stream = match ctx.handshake(stream) { + Ok(_) => panic!("unexpected success"), + Err(HandshakeError::Interrupted(stream)) => stream, + Err(err) => panic!("unexpected error {err:?}"), + }; + + assert!(stream.server_auth_completed()); + let mut peer_trust = p!(stream.context().peer_trust2()).unwrap(); + p!(peer_trust.set_anchor_certificates(&[certificate()])); + p!(peer_trust.evaluate_with_error()); + + let mut stream = p!(stream.handshake()); + p!(stream.write_all(b"hello world!")); + + handle.join().unwrap(); + } + + #[test] + #[ignore] + fn server_client_builders() { + let listener = p!(TcpListener::bind("localhost:0")); + let port = p!(listener.local_addr()).port(); + + let handle = thread::spawn(move || { + let dir = p!(tempdir()); + + let identity = identity(dir.path()); + let builder = ServerBuilder::new(&identity, &[]); + + let stream = p!(listener.accept()).0; + let mut stream = p!(builder.handshake(stream)); + + let mut buf = [0; 12]; + p!(stream.read(&mut buf)); + assert_eq!(&buf[..], b"hello world!"); + }); + + let stream = p!(TcpStream::connect(("localhost", port))); + let mut stream = p!(ClientBuilder::new() + .anchor_certificates(&[certificate()]) + .handshake("foobar.com", stream)); + + p!(stream.write_all(b"hello world!")); + + handle.join().unwrap(); + } + + #[test] + fn client_bad_cert() { + let _ = env_logger::try_init(); + + let listener = p!(TcpListener::bind("localhost:0")); + let port = p!(listener.local_addr()).port(); + + let handle = thread::spawn(move || { + let dir = p!(tempdir()); + + let mut ctx = p!(SslContext::new( + SslProtocolSide::SERVER, + SslConnectionType::STREAM + )); + let identity = identity(dir.path()); + p!(ctx.set_certificate(&identity, &[])); + + let stream = p!(listener.accept()).0; + let _ = ctx.handshake(stream); + }); + + let stream = p!(TcpStream::connect(("localhost", port))); + assert!(ClientBuilder::new() + .handshake("foobar.com", stream) + .is_err()); + + handle.join().unwrap(); + } + + #[test] + #[ignore] + fn client() { + let listener = p!(TcpListener::bind("localhost:0")); + let port = p!(listener.local_addr()).port(); + + let handle = thread::spawn(move || { + let dir = p!(tempdir()); + + let mut ctx = p!(SslContext::new( + SslProtocolSide::SERVER, + SslConnectionType::STREAM + )); + let identity = identity(dir.path()); + p!(ctx.set_certificate(&identity, &[])); + + let stream = p!(listener.accept()).0; + let mut stream = p!(ctx.handshake(stream)); + + let mut buf = [0; 12]; + p!(stream.read(&mut buf)); + assert_eq!(&buf[..], b"hello world!"); + }); + + let stream = p!(TcpStream::connect(("localhost", port))); + let mut stream = p!(ClientBuilder::new() + .anchor_certificates(&[certificate()]) + .handshake("foobar.com", stream)); + p!(stream.write_all(b"hello world!")); + + handle.join().unwrap(); + } + + #[test] + fn negotiated_cipher() { + let listener = p!(TcpListener::bind("localhost:0")); + let port = p!(listener.local_addr()).port(); + + let handle = thread::spawn(move || { + let dir = p!(tempdir()); + + let mut ctx = p!(SslContext::new( + SslProtocolSide::SERVER, + SslConnectionType::STREAM + )); + let identity = identity(dir.path()); + p!(ctx.set_certificate(&identity, &[])); + p!(ctx.set_enabled_ciphers(&[ + CipherSuite::TLS_DHE_RSA_WITH_AES_256_CBC_SHA256, + CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 + ])); + + let stream = p!(listener.accept()).0; + let mut stream = p!(ctx.handshake(stream)); + assert_eq!( + CipherSuite::TLS_DHE_RSA_WITH_AES_256_CBC_SHA256, + p!(stream.context().negotiated_cipher()) + ); + let mut buf = [0; 1]; + p!(stream.read(&mut buf)); + }); + + let mut ctx = p!(SslContext::new( + SslProtocolSide::CLIENT, + SslConnectionType::STREAM + )); + p!(ctx.set_break_on_server_auth(true)); + p!(ctx.set_enabled_ciphers(&[ + CipherSuite::TLS_DHE_PSK_WITH_AES_128_CBC_SHA256, + CipherSuite::TLS_DHE_RSA_WITH_AES_256_CBC_SHA256 + ])); + let stream = p!(TcpStream::connect(("localhost", port))); + + let stream = match ctx.handshake(stream) { + Ok(_) => panic!("unexpected success"), + Err(HandshakeError::Interrupted(stream)) => stream, + Err(err) => panic!("unexpected error {err:?}"), + }; + + let mut stream = p!(stream.handshake()); + assert_eq!( + CipherSuite::TLS_DHE_RSA_WITH_AES_256_CBC_SHA256, + p!(stream.context().negotiated_cipher()) + ); + p!(stream.write(&[0])); + + handle.join().unwrap(); + } + + #[test] + fn dh_params() { + let params = include_bytes!("../../../test/dhparam.der"); + + let mut ctx = p!(SslContext::new( + SslProtocolSide::SERVER, + SslConnectionType::STREAM + )); + assert!(p!(ctx.diffie_hellman_params()).is_none()); + p!(ctx.set_diffie_hellman_params(params)); + assert_eq!(p!(ctx.diffie_hellman_params()).unwrap(), ¶ms[..]); + } + + #[test] + fn try_authenticate_no_cert() { + let listener = p!(TcpListener::bind("localhost:0")); + let port = p!(listener.local_addr()).port(); + + let handle = thread::spawn(move || { + let dir = p!(tempdir()); + + let mut ctx = p!(SslContext::new( + SslProtocolSide::SERVER, + SslConnectionType::STREAM + )); + let identity = identity(dir.path()); + p!(ctx.set_certificate(&identity, &[])); + p!(ctx.set_client_side_authenticate(SslAuthenticate::TRY)); + let cert = certificate(); + p!(ctx.add_certificate_authorities(&[cert])); + + let stream = p!(listener.accept()).0; + let mut stream = p!(ctx.handshake(stream)); + let mut buf = [0; 1]; + p!(stream.read(&mut buf)); + }); + + let mut ctx = p!(SslContext::new( + SslProtocolSide::CLIENT, + SslConnectionType::STREAM + )); + p!(ctx.set_break_on_server_auth(true)); + let stream = p!(TcpStream::connect(("localhost", port))); + + let stream = match ctx.handshake(stream) { + Ok(_) => panic!("unexpected success"), + Err(HandshakeError::Interrupted(stream)) => stream, + Err(err) => panic!("unexpected error {err:?}"), + }; + + let mut stream = p!(stream.handshake()); + p!(stream.write(&[0])); + + handle.join().unwrap(); + } + + #[test] + fn always_authenticate_no_cert() { + let listener = p!(TcpListener::bind("localhost:0")); + let port = p!(listener.local_addr()).port(); + + let handle = thread::spawn(move || { + let dir = p!(tempdir()); + + let mut ctx = p!(SslContext::new( + SslProtocolSide::SERVER, + SslConnectionType::STREAM + )); + let identity = identity(dir.path()); + p!(ctx.set_certificate(&identity, &[])); + p!(ctx.set_client_side_authenticate(SslAuthenticate::ALWAYS)); + + let stream = p!(listener.accept()).0; + + match ctx.handshake(stream) { + Ok(_) => panic!("unexpected success"), + Err(HandshakeError::Failure(_)) => {} + Err(err) => panic!("unexpected error {err:?}"), + } + }); + + let mut ctx = p!(SslContext::new( + SslProtocolSide::CLIENT, + SslConnectionType::STREAM + )); + p!(ctx.set_break_on_server_auth(true)); + let stream = p!(TcpStream::connect(("localhost", port))); + + let stream = match ctx.handshake(stream) { + Ok(_) => panic!("unexpected success"), + Err(HandshakeError::Interrupted(stream)) => stream, + Err(err) => panic!("unexpected error {err:?}"), + }; + + match stream.handshake() { + Ok(_) => panic!("unexpected success"), + Err(HandshakeError::Failure(_)) => {} + Err(err) => panic!("unexpected error {err:?}"), + } + + handle.join().unwrap(); + } + + #[test] + fn always_authenticate_with_cert() { + let listener = p!(TcpListener::bind("localhost:0")); + let port = p!(listener.local_addr()).port(); + + let handle = thread::spawn(move || { + let dir = p!(tempdir()); + + let mut ctx = p!(SslContext::new( + SslProtocolSide::SERVER, + SslConnectionType::STREAM + )); + let identity = identity(dir.path()); + p!(ctx.set_certificate(&identity, &[])); + p!(ctx.set_client_side_authenticate(SslAuthenticate::ALWAYS)); + + let stream = p!(listener.accept()).0; + + match ctx.handshake(stream) { + Ok(_) => panic!("unexpected success"), + Err(HandshakeError::Failure(_)) => {} + Err(err) => panic!("unexpected error {err:?}"), + } + }); + + let mut ctx = p!(SslContext::new( + SslProtocolSide::CLIENT, + SslConnectionType::STREAM + )); + p!(ctx.set_break_on_server_auth(true)); + let dir = p!(tempdir()); + let identity = identity(dir.path()); + p!(ctx.set_certificate(&identity, &[])); + let stream = p!(TcpStream::connect(("localhost", port))); + + let stream = match ctx.handshake(stream) { + Ok(_) => panic!("unexpected success"), + Err(HandshakeError::Interrupted(stream)) => stream, + Err(err) => panic!("unexpected error {err:?}"), + }; + + match stream.handshake() { + Ok(_) => panic!("unexpected success"), + Err(HandshakeError::Failure(_)) => {} + Err(err) => panic!("unexpected error {err:?}"), + } + + handle.join().unwrap(); + } + + #[test] + fn certificate_authorities() { + let mut ctx = p!(SslContext::new( + SslProtocolSide::SERVER, + SslConnectionType::STREAM + )); + assert!(p!(ctx.certificate_authorities()).is_none()); + p!(ctx.set_certificate_authorities(&[certificate()])); + assert_eq!(p!(ctx.certificate_authorities()).unwrap().len(), 1); + } + + #[test] + #[ignore] + fn close() { + let listener = p!(TcpListener::bind("localhost:0")); + let port = p!(listener.local_addr()).port(); + + let handle = thread::spawn(move || { + let dir = p!(tempdir()); + + let identity = identity(dir.path()); + let builder = ServerBuilder::new(&identity, &[]); + + let stream = p!(listener.accept()).0; + let mut stream = p!(builder.handshake(stream)); + p!(stream.close()); + }); + + let stream = p!(TcpStream::connect(("localhost", port))); + let mut stream = p!(ClientBuilder::new() + .anchor_certificates(&[certificate()]) + .handshake("foobar.com", stream)); + + let mut buf = [0; 1]; + assert_eq!(p!(stream.read(&mut buf)), 0); + p!(stream.close()); + + p!(handle.join()); + } + + #[test] + #[ignore] + fn short_read() { + let listener = p!(TcpListener::bind("localhost:0")); + let port = p!(listener.local_addr()).port(); + + let handle = thread::spawn(move || { + let dir = p!(tempdir()); + + let identity = identity(dir.path()); + let builder = ServerBuilder::new(&identity, &[]); + + let stream = p!(listener.accept()).0; + let mut stream = p!(builder.handshake(stream)); + + stream.write_all(b"hello").unwrap(); + // make sure stream doesn't close + stream + }); + + let stream = p!(TcpStream::connect(("localhost", port))); + let mut stream = p!(ClientBuilder::new() + .anchor_certificates(&[certificate()]) + .handshake("foobar.com", stream)); + + let mut b = [0; 1]; + stream.read_exact(&mut b).unwrap(); + assert_eq!(stream.context().buffered_read_size().unwrap(), 4); + let mut b = [0; 5]; + let read = stream.read(&mut b).unwrap(); + assert_eq!(read, 4); + + p!(handle.join()); + } +} diff --git a/vendor/security-framework/src/os/macos/transform.rs b/vendor/security-framework/src/os/macos/transform.rs new file mode 100644 index 00000000..d03bc1f7 --- /dev/null +++ b/vendor/security-framework/src/os/macos/transform.rs @@ -0,0 +1,54 @@ +//! Transform support + +use core_foundation::base::{CFType, TCFType}; +use core_foundation::error::CFError; +use core_foundation::string::CFString; +use security_framework_sys::transform::*; +use std::ptr; + +declare_TCFType! { + /// A type representing a transform. + SecTransform, SecTransformRef +} +impl_TCFType!(SecTransform, SecTransformRef, SecTransformGetTypeID); + +unsafe impl Sync for SecTransform {} +unsafe impl Send for SecTransform {} + +impl SecTransform { + /// Sets an attribute of the transform. + pub fn set_attribute<T>(&mut self, key: &CFString, value: &T) -> Result<(), CFError> + where + T: TCFType, + { + unsafe { + let mut error = ptr::null_mut(); + SecTransformSetAttribute( + self.0, + key.as_concrete_TypeRef(), + value.as_CFTypeRef(), + &mut error, + ); + if !error.is_null() { + return Err(CFError::wrap_under_create_rule(error)); + } + + Ok(()) + } + } + + /// Executes the transform. + /// + /// The return type depends on the type of transform. + pub fn execute(&mut self) -> Result<CFType, CFError> { + unsafe { + let mut error = ptr::null_mut(); + let result = SecTransformExecute(self.0, &mut error); + if result.is_null() { + return Err(CFError::wrap_under_create_rule(error)); + } + + Ok(CFType::wrap_under_create_rule(result)) + } + } +} diff --git a/vendor/security-framework/src/os/mod.rs b/vendor/security-framework/src/os/mod.rs new file mode 100644 index 00000000..ec7e38b3 --- /dev/null +++ b/vendor/security-framework/src/os/mod.rs @@ -0,0 +1,4 @@ +//! OS specific extensions. + +#[cfg(target_os = "macos")] +pub mod macos; |
