diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-30 03:59:35 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-30 03:59:35 +0000 |
commit | d1b2d29528b7794b41e66fc2136e395a02f8529b (patch) | |
tree | a4a17504b260206dec3cf55b2dca82929a348ac2 /vendor/security-framework/src/trust_settings.rs | |
parent | Releasing progress-linux version 1.72.1+dfsg1-1~progress7.99u1. (diff) | |
download | rustc-d1b2d29528b7794b41e66fc2136e395a02f8529b.tar.xz rustc-d1b2d29528b7794b41e66fc2136e395a02f8529b.zip |
Merging upstream version 1.73.0+dfsg1.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vendor/security-framework/src/trust_settings.rs')
-rw-r--r-- | vendor/security-framework/src/trust_settings.rs | 305 |
1 files changed, 305 insertions, 0 deletions
diff --git a/vendor/security-framework/src/trust_settings.rs b/vendor/security-framework/src/trust_settings.rs new file mode 100644 index 000000000..23efdf1f7 --- /dev/null +++ b/vendor/security-framework/src/trust_settings.rs @@ -0,0 +1,305 @@ +//! Querying trust settings. + +use core_foundation::array::{CFArray, CFArrayRef}; +use core_foundation::base::{CFIndex, TCFType}; +use core_foundation::dictionary::CFDictionary; +use core_foundation::number::CFNumber; +use core_foundation::string::CFString; + +use core_foundation_sys::base::CFTypeRef; +use security_framework_sys::base::errSecNoTrustSettings; +use security_framework_sys::base::errSecSuccess; +use security_framework_sys::trust_settings::*; + +use std::ptr; + +use crate::base::Error; +use crate::base::Result; +use crate::certificate::SecCertificate; +use crate::cvt; + +/// Which set of trust settings to query +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[repr(u32)] +pub enum Domain { + /// Per-user trust settings + User = kSecTrustSettingsDomainUser, + /// Locally administered, system-wide trust settings + Admin = kSecTrustSettingsDomainAdmin, + /// System trust settings + System = kSecTrustSettingsDomainSystem, +} + +impl From<Domain> for SecTrustSettingsDomain { + #[inline] + fn from(domain: Domain) -> SecTrustSettingsDomain { + match domain { + Domain::User => kSecTrustSettingsDomainUser, + Domain::Admin => kSecTrustSettingsDomainAdmin, + Domain::System => kSecTrustSettingsDomainSystem, + } + } +} + +/// Trust settings for a specific certificate in a specific domain +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum TrustSettingsForCertificate { + /// Not used + Invalid, + + /// This is a root certificate and is trusted, either explicitly or + /// implicitly. + TrustRoot, + + /// This is a non-root certificate but is explicitly trusted. + TrustAsRoot, + + /// Cert is explicitly distrusted. + Deny, + + /// Neither trusted nor distrusted. + Unspecified, +} + +impl TrustSettingsForCertificate { + /// Create from `kSecTrustSettingsResult*` constant + fn new(value: i64) -> Self { + if value < 0 || value > i64::from(u32::max_value()) { + return Self::Invalid; + } + match value as u32 { + kSecTrustSettingsResultTrustRoot => Self::TrustRoot, + kSecTrustSettingsResultTrustAsRoot => Self::TrustAsRoot, + kSecTrustSettingsResultDeny => Self::Deny, + kSecTrustSettingsResultUnspecified => Self::Unspecified, + _ => Self::Invalid, + } + } +} + +/// Allows access to the certificates and their trust settings in a given domain. +pub struct TrustSettings { + domain: Domain, +} + +impl TrustSettings { + /// Create a new `TrustSettings` for the given domain. + /// + /// You can call `iter()` to discover the certificates with settings in this domain. + /// + /// Then you can call `tls_trust_settings_for_certificate()` with a given certificate + /// to learn what the aggregate trust setting for that certificate within this domain. + #[inline(always)] + #[must_use] + pub fn new(domain: Domain) -> Self { + Self { domain } + } + + /// Create an iterator over the certificates with settings in this domain. + /// This produces an empty iterator if there are no such certificates. + pub fn iter(&self) -> Result<TrustSettingsIter> { + let array = unsafe { + let mut array_ptr: CFArrayRef = ptr::null_mut(); + + // SecTrustSettingsCopyCertificates returns errSecNoTrustSettings + // if no items have trust settings in the given domain. We map + // that to an empty TrustSettings iterator. + match SecTrustSettingsCopyCertificates(self.domain.into(), &mut array_ptr) { + errSecNoTrustSettings => CFArray::from_CFTypes(&[]), + errSecSuccess => CFArray::<SecCertificate>::wrap_under_create_rule(array_ptr), + err => return Err(Error::from_code(err)), + } + }; + + Ok(TrustSettingsIter { index: 0, array }) + } + + ///set trust settings to ""always trust this root certificate regardless of use.". + /// Sets the trust settings for the provided certificate to "always trust this root certificate + /// regardless of use." + /// + /// This method configures the trust settings for the specified certificate, indicating that it should + /// always be trusted as a TLS root certificate, regardless of its usage. + /// + /// If successful, the trust settings are updated for the certificate in the given domain. If the + /// certificate had no previous trust settings in the domain, new trust settings are created. If the + /// certificate had existing trust settings, they are replaced with the new settings. + /// + /// It is not possible to modify per-user trust settings when not running in a GUI + /// environment, if you try it will return error `2070: errSecInternalComponent` + #[cfg(target_os="macos")] + pub fn set_trust_settings_always(&self, cert: &SecCertificate) -> Result<()> { + let domain = self.domain; + let trust_settings: CFTypeRef = ptr::null_mut(); + cvt(unsafe { + SecTrustSettingsSetTrustSettings( + cert.as_CFTypeRef() as *mut _, + domain.into(), + trust_settings, + ) + }) + } + + /// Returns the aggregate trust setting for the given certificate. + /// + /// This tells you whether the certificate should be trusted as a TLS + /// root certificate. + /// + /// If the certificate has no trust settings in the given domain, the + /// `errSecItemNotFound` error is returned. + /// + /// If the certificate has no specific trust settings for TLS in the + /// given domain `None` is returned. + /// + /// Otherwise, the specific trust settings are aggregated and returned. + pub fn tls_trust_settings_for_certificate(&self, cert: &SecCertificate) + -> Result<Option<TrustSettingsForCertificate>> { + let trust_settings = unsafe { + let mut array_ptr: CFArrayRef = ptr::null_mut(); + let cert_ptr = cert.as_CFTypeRef() as *mut _; + cvt(SecTrustSettingsCopyTrustSettings(cert_ptr, + self.domain.into(), + &mut array_ptr))?; + CFArray::<CFDictionary>::wrap_under_create_rule(array_ptr) + }; + + for settings in trust_settings.iter() { + // Reject settings for non-SSL policies + let is_not_ssl_policy = { + let policy_name_key = CFString::from_static_string("kSecTrustSettingsPolicyName"); + let ssl_policy_name = CFString::from_static_string("sslServer"); + + let maybe_name: Option<CFString> = settings + .find(policy_name_key.as_CFTypeRef().cast()) + .map(|name| unsafe { CFString::wrap_under_get_rule((*name).cast()) }); + + matches!(maybe_name, Some(ref name) if name != &ssl_policy_name) + }; + + if is_not_ssl_policy { + continue; + } + + // Evaluate "effective trust settings" for this usage constraint. + let maybe_trust_result = { + let settings_result_key = CFString::from_static_string("kSecTrustSettingsResult"); + settings + .find(settings_result_key.as_CFTypeRef().cast()) + .map(|num| unsafe { CFNumber::wrap_under_get_rule((*num).cast()) }) + .and_then(|num| num.to_i64()) + }; + + // "Note that an empty Trust Settings array means "always trust this cert, + // with a resulting kSecTrustSettingsResult of kSecTrustSettingsResultTrustRoot"." + let trust_result = TrustSettingsForCertificate::new(maybe_trust_result + .unwrap_or_else(|| i64::from(kSecTrustSettingsResultTrustRoot))); + + match trust_result { + TrustSettingsForCertificate::Unspecified | + TrustSettingsForCertificate::Invalid => { continue; }, + _ => return Ok(Some(trust_result)), + } + } + + // There were no more specific settings. This might mean the certificate + // is to be trusted anyway (since, eg, it's in system store), but leave + // the caller to make this decision. + Ok(None) + } +} + +/// Iterator over certificates. +pub struct TrustSettingsIter { + array: CFArray<SecCertificate>, + index: CFIndex, +} + +impl Iterator for TrustSettingsIter { + type Item = SecCertificate; + + #[inline] + fn next(&mut self) -> Option<Self::Item> { + if self.index >= self.array.len() { + None + } else { + let cert = self.array.get(self.index).unwrap(); + self.index += 1; + Some(cert.clone()) + } + } + + #[inline] + fn size_hint(&self) -> (usize, Option<usize>) { + let left = (self.array.len() as usize).saturating_sub(self.index as usize); + (left, Some(left)) + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::test::certificate; + + fn list_for_domain(domain: Domain) { + println!("--- domain: {:?}", domain); + let ts = TrustSettings::new(domain); + let iterator = ts.iter().unwrap(); + + for (i, cert) in iterator.enumerate() { + println!("cert({:?}) = {:?}", i, cert); + println!(" settings = {:?}", ts.tls_trust_settings_for_certificate(&cert)); + } + println!("---"); + } + + #[test] + fn list_for_user() { + list_for_domain(Domain::User); + } + + #[test] + fn list_for_system() { + list_for_domain(Domain::System); + } + + #[test] + fn list_for_admin() { + list_for_domain(Domain::Admin); + } + + #[test] + fn test_system_certs_are_present() { + let system = TrustSettings::new(Domain::System).iter().unwrap().count(); + + // 168 at the time of writing + assert!(system > 100); + } + + #[test] + fn test_isrg_root_exists_and_is_trusted() { + let ts = TrustSettings::new(Domain::System); + assert_eq!( + ts.iter() + .unwrap() + .find(|cert| cert.subject_summary() == "ISRG Root X1") + .and_then(|cert| ts.tls_trust_settings_for_certificate(&cert).unwrap()), + None + ); + // ^ this is a case where None means "always trust", according to Apple docs: + // + // "Note that an empty Trust Settings array means "always trust this cert, + // with a resulting kSecTrustSettingsResult of kSecTrustSettingsResultTrustRoot"." + } + + #[test] + fn test_unknown_cert_is_not_trusted() { + let ts = TrustSettings::new(Domain::System); + let cert = certificate(); + assert_eq!(ts.tls_trust_settings_for_certificate(&cert) + .err() + .unwrap() + .message(), + Some("The specified item could not be found in the keychain.".into())); + } +} + |