deno_native_certs 0.1.0

deno-native-certs loads native certificate store into rustls
Documentation
use crate::Certificate;
use dlopen::symbor::{Container, Ref, SymBorApi, Symbol};
use dlopen::Error as DlopenError;
use std::collections::HashMap;
use std::ffi::c_char;
use std::ffi::c_void;
use std::io::Error;
use std::ptr;

#[repr(C)]
pub struct __CFArray(c_void);

pub type CFArrayRef = *const __CFArray;

struct Array<'a, T> {
  array: CFArrayRef,
  cf: &'a CoreFoundation<'a>,
  _marker: std::marker::PhantomData<T>,
}

impl<'a, T> Array<'a, T> {
  fn new(array: CFArrayRef, cf: &'a CoreFoundation<'a>) -> Self {
    Self {
      array,
      cf,
      _marker: std::marker::PhantomData,
    }
  }

  fn len(&self) -> usize {
    unsafe { (self.cf.CFArrayGetCount)(self.array) as usize }
  }

  fn get(&self, index: usize) -> *mut T {
    unsafe {
      (self.cf.CFArrayGetValueAtIndex)(self.array, index as CFIndex) as *mut T
    }
  }

  fn iter(&'a self) -> ArrayIter<'a, T> {
    ArrayIter {
      array: self,
      index: 0,
    }
  }
}

struct ArrayIter<'a, T> {
  array: &'a Array<'a, T>,
  index: usize,
}

impl<'a, T> Iterator for ArrayIter<'a, T> {
  type Item = *mut T;

  fn next(&mut self) -> Option<Self::Item> {
    if self.index < self.array.len() {
      let value = self.array.get(self.index);
      self.index += 1;
      Some(value)
    } else {
      None
    }
  }
}

#[repr(C)]
pub struct __CFData(c_void);

pub type CFDataRef = *const __CFData;

#[repr(C)]
pub struct __CFDictionary(c_void);

pub type CFDictionaryRef = *const __CFDictionary;

#[repr(C)]
pub struct __CFString(c_void);

pub type CFStringRef = *const __CFString;

struct CFString<'a> {
  string: CFStringRef,
  cf: &'a CoreFoundation<'a>,
}

impl<'a> CFString<'a> {
  fn from_raw(string: CFStringRef, cf: &'a CoreFoundation<'a>) -> Self {
    Self { string, cf }
  }

  fn from_static_str(s: &'static str, cf: &'a CoreFoundation<'a>) -> Self {
    Self {
      string: unsafe {
        (cf.CFStringCreateWithBytesNoCopy)(
          *cf.kCFAllocatorDefault,
          s.as_ptr() as _,
          s.len() as _,
          kCFStringEncodingUTF8,
          0,
          *cf.kCFAllocatorNull,
        )
      },
      cf,
    }
  }
}

impl PartialEq for CFString<'_> {
  fn eq(&self, other: &Self) -> bool {
    unsafe { (self.cf.CFEqual)(self.string as _, other.string as _) != 0 }
  }
}

type CFAllocatorRef = *const c_void;
type CFTypeRef = *const c_void;
type OSStatus = i32;
type CFIndex = isize;

type SecTrustSettingsDomain = u32;

const kSecTrustSettingsDomainUser: SecTrustSettingsDomain = 0;
const kSecTrustSettingsDomainAdmin: SecTrustSettingsDomain = 1;
const kSecTrustSettingsDomainSystem: SecTrustSettingsDomain = 2;

type SecTrustSettingsResult = u32;

const kSecTrustSettingsResultInvalid: SecTrustSettingsResult = 0;
const kSecTrustSettingsResultTrustRoot: SecTrustSettingsResult = 1;
const kSecTrustSettingsResultTrustAsRoot: SecTrustSettingsResult = 2;
const kSecTrustSettingsResultDeny: SecTrustSettingsResult = 3;
const kSecTrustSettingsResultUnspecified: SecTrustSettingsResult = 4;

const errSecNoTrustSettings: OSStatus = -25263;
const errSecSuccess: OSStatus = 0;

enum OpaqueSecCertificateRef {}
type SecCertificateRef = *mut OpaqueSecCertificateRef;

#[allow(non_snake_case)]
#[derive(dlopen_derive::SymBorApi)]
struct TrustSettings<'a> {
  // TrustSettings
  SecTrustSettingsCopyCertificates: Symbol<
    'a,
    unsafe extern "C" fn(SecTrustSettingsDomain, *mut CFArrayRef) -> OSStatus,
  >,
  SecTrustSettingsCopyTrustSettings: Symbol<
    'a,
    unsafe extern "C" fn(
      SecCertificateRef,
      SecTrustSettingsDomain,
      *mut CFArrayRef,
    ) -> OSStatus,
  >,
  // Certificate
  SecCertificateCopyData:
    Symbol<'a, unsafe extern "C" fn(SecCertificateRef) -> CFDataRef>,
}

type CFStringEncoding = u32;
static kCFStringEncodingUTF8: CFStringEncoding = 0x08000100;

#[allow(non_snake_case)]
#[derive(dlopen_derive::SymBorApi)]
struct CoreFoundation<'a> {
  // CFArray
  CFArrayGetValueAtIndex:
    Symbol<'a, unsafe extern "C" fn(CFArrayRef, CFIndex) -> *const c_void>,
  CFArrayGetCount: Symbol<'a, unsafe extern "C" fn(CFArrayRef) -> CFIndex>,
  CFDictionaryGetValueIfPresent: Symbol<
    'a,
    unsafe extern "C" fn(
      CFDictionaryRef,
      *const c_void,
      *mut *const c_void,
    ) -> u8,
  >,
  CFStringCreateWithBytesNoCopy: Symbol<
    'a,
    unsafe extern "C" fn(
      CFAllocatorRef,
      *const u8,
      CFIndex,
      CFStringEncoding,
      u8,
      CFAllocatorRef,
    ) -> CFStringRef,
  >,
  CFDataGetBytePtr:
    Symbol<'a, unsafe extern "C" fn(theData: CFDataRef) -> *const u8>,
  CFDataGetLength:
    Symbol<'a, unsafe extern "C" fn(theData: CFDataRef) -> CFIndex>,
  CFEqual: Symbol<'a, unsafe extern "C" fn(CFTypeRef, CFTypeRef) -> u8>,
  kCFAllocatorDefault: Ref<'a, CFAllocatorRef>,
  kCFAllocatorNull: Ref<'a, CFAllocatorRef>,
}

fn find_frameworks() -> Result<
  (
    Container<TrustSettings<'static>>,
    Container<CoreFoundation<'static>>,
  ),
  DlopenError,
> {
  unsafe {
    Ok((
            Container::load(
                "/System/Library/Frameworks/Security.framework/Versions/Current/Security",
            )?,
            Container::load(
                "/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation",
            )?,
        ))
  }
}

pub fn load_native_certs() -> Result<Vec<Certificate>, Error> {
  let (framework, cf) = find_frameworks().unwrap();

  let mut all_certs = HashMap::new();

  for domain in [
    kSecTrustSettingsDomainUser,
    kSecTrustSettingsDomainAdmin,
    kSecTrustSettingsDomainSystem,
  ] {
    let mut array_ptr: CFArrayRef = ptr::null_mut();
    match unsafe {
      (framework.SecTrustSettingsCopyCertificates)(domain, &mut array_ptr)
    } {
      errSecNoTrustSettings => continue,
      errSecSuccess => {}
      _ => panic!("HUH"),
    };

    let certs: Array<OpaqueSecCertificateRef> = Array::new(array_ptr, &cf);
    for cert in certs.iter() {
      let der = unsafe { (framework.SecCertificateCopyData)(cert) };

      let trusted = unsafe {
        let mut array_ptr: CFArrayRef = ptr::null_mut();
        (framework.SecTrustSettingsCopyTrustSettings)(
          cert,
          domain,
          &mut array_ptr,
        );

        let settings: Array<__CFDictionary> = Array::new(array_ptr, &cf);
        tls_trust_settings_for_certificates(&cf, settings)
          .unwrap_or(kSecTrustSettingsResultTrustRoot)
      };

      all_certs.entry(der).or_insert(trusted);
    }
  }

  let mut certs = Vec::new();

  for (der, trusted) in all_certs.drain() {
    if let kSecTrustSettingsResultTrustRoot
    | kSecTrustSettingsResultTrustAsRoot = trusted
    {
      certs.push(Certificate(
        unsafe {
          std::slice::from_raw_parts(
            (cf.CFDataGetBytePtr)(der),
            (cf.CFDataGetLength)(der) as usize,
          )
        }
        .to_vec(),
      ));
    }
  }

  Ok(certs)
}

unsafe fn tls_trust_settings_for_certificates<'a>(
  cf: &'a CoreFoundation<'a>,
  settings: Array<'a, __CFDictionary>,
) -> Option<SecTrustSettingsResult> {
  for dict in settings.iter() {
    let policy_name_key =
      CFString::from_static_str("kSecTrustSettingsPolicyName", &cf);
    let ssl_policy_name = CFString::from_static_str("sslServer", &cf);

    let maybe_name = {
      let mut value: *const c_void = ptr::null();
      if (cf.CFDictionaryGetValueIfPresent)(
        dict,
        policy_name_key.string as _,
        &mut value,
      ) != 0
      {
        Some(CFString::from_raw(value as _, &cf))
      } else {
        None
      }
    };

    if matches!(maybe_name, Some(ref name) if name != &ssl_policy_name) {
      continue;
    }

    let settings_result_key =
      CFString::from_static_str("kSecTrustSettingsResult", &cf);
    let mut value: *const c_void = ptr::null();
    let trust_result = if (cf.CFDictionaryGetValueIfPresent)(
      dict,
      settings_result_key.string as _,
      &mut value,
    ) != 0
    {
      value as SecTrustSettingsResult
    } else {
      kSecTrustSettingsResultTrustRoot
    };

    match trust_result {
      kSecTrustSettingsResultDeny
      | kSecTrustSettingsResultTrustRoot
      | kSecTrustSettingsResultTrustAsRoot => {
        return Some(trust_result);
      }
      _ => continue,
    }
  }

  None
}

#[cfg(test)]
mod tests {
  use super::*;

  #[test]
  fn load() {
    let mut actual = load_native_certs()
      .unwrap()
      .into_iter()
      .map(|cert| cert.0)
      .collect::<Vec<_>>();
    let mut expected = rustls_native_certs::load_native_certs()
      .unwrap()
      .into_iter()
      .map(|cert| cert.0)
      .collect::<Vec<_>>();

    actual.sort();
    expected.sort();
    assert_eq!(actual, expected);
  }
}