summaryrefslogtreecommitdiff
path: root/vendor/security-framework/src/os
diff options
context:
space:
mode:
authormo khan <mo@mokhan.ca>2025-07-10 13:11:11 -0600
committermo khan <mo@mokhan.ca>2025-07-10 13:11:11 -0600
commit01959b16a21b22b5df5f16569c2a8e8f92beecef (patch)
tree32afa5d747c5466345c59ec52161a7cba3d6d755 /vendor/security-framework/src/os
parentff30574117a996df332e23d1fb6f65259b316b5b (diff)
chore: vendor dependencies
Diffstat (limited to 'vendor/security-framework/src/os')
-rw-r--r--vendor/security-framework/src/os/macos/access.rs14
-rw-r--r--vendor/security-framework/src/os/macos/certificate.rs267
-rw-r--r--vendor/security-framework/src/os/macos/certificate_oids.rs32
-rw-r--r--vendor/security-framework/src/os/macos/code_signing.rs486
-rw-r--r--vendor/security-framework/src/os/macos/digest_transform.rs194
-rw-r--r--vendor/security-framework/src/os/macos/encrypt_transform.rs254
-rw-r--r--vendor/security-framework/src/os/macos/identity.rs85
-rw-r--r--vendor/security-framework/src/os/macos/import_export.rs344
-rw-r--r--vendor/security-framework/src/os/macos/item.rs47
-rw-r--r--vendor/security-framework/src/os/macos/key.rs38
-rw-r--r--vendor/security-framework/src/os/macos/keychain.rs281
-rw-r--r--vendor/security-framework/src/os/macos/keychain_item.rs26
-rw-r--r--vendor/security-framework/src/os/macos/mod.rs52
-rw-r--r--vendor/security-framework/src/os/macos/passwords.rs525
-rw-r--r--vendor/security-framework/src/os/macos/secure_transport.rs647
-rw-r--r--vendor/security-framework/src/os/macos/transform.rs54
-rw-r--r--vendor/security-framework/src/os/mod.rs4
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(), &params[..]);
+ }
+
+ #[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;