summaryrefslogtreecommitdiffstats
path: root/vendor/security-framework/src/trust_settings.rs
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-30 03:59:35 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-30 03:59:35 +0000
commitd1b2d29528b7794b41e66fc2136e395a02f8529b (patch)
treea4a17504b260206dec3cf55b2dca82929a348ac2 /vendor/security-framework/src/trust_settings.rs
parentReleasing progress-linux version 1.72.1+dfsg1-1~progress7.99u1. (diff)
downloadrustc-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.rs305
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()));
+ }
+}
+