Skip to content

Commit

Permalink
Merge pull request #585 from inejge/cacert
Browse files Browse the repository at this point in the history
Improve rustls CA certificate loading
  • Loading branch information
brson authored Jul 15, 2016
2 parents 7d7fed2 + d54a9d5 commit fe1cc85
Show file tree
Hide file tree
Showing 10 changed files with 395 additions and 10 deletions.
11 changes: 11 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 14 additions & 0 deletions src/ca-loader/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[package]
name = "ca-loader"
version = "0.1.0"
authors = [ "Ivan Nejgebauer <inejge@gmail.com>" ]

[dependencies]
libc = "0.2"

[target."cfg(windows)".dependencies]
winapi = "0.2.8"
crypt32-sys = "0.2"

[target.'cfg(target_os = "macos")'.dependencies]
security-framework = "0.1.5"
10 changes: 10 additions & 0 deletions src/ca-loader/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#[macro_use]
mod macros;
mod sys;

pub use self::sys::CertBundle;

pub enum CertItem {
File(String),
Blob(Vec<u8>)
}
38 changes: 38 additions & 0 deletions src/ca-loader/src/macros.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Taken from the libc crate, see <https://github.com/rust-lang/libc> for
// authorship and copyright information.

// A macro for defining #[cfg] if-else statements.
//
// This is similar to the `if/elif` C preprocessor macro by allowing definition
// of a cascade of `#[cfg]` cases, emitting the implementation which matches
// first.
//
// This allows you to conveniently provide a long list #[cfg]'d blocks of code
// without having to rewrite each clause multiple times.
macro_rules! cfg_if {
($(
if #[cfg($($meta:meta),*)] { $($it:item)* }
) else * else {
$($it2:item)*
}) => {
__cfg_if_items! {
() ;
$( ( ($($meta),*) ($($it)*) ), )*
( () ($($it2)*) ),
}
}
}

macro_rules! __cfg_if_items {
(($($not:meta,)*) ; ) => {};
(($($not:meta,)*) ; ( ($($m:meta),*) ($($it:item)*) ), $($rest:tt)*) => {
__cfg_if_apply! { cfg(all(not(any($($not),*)), $($m,)*)), $($it)* }
__cfg_if_items! { ($($not,)* $($m,)*) ; $($rest)* }
}
}

macro_rules! __cfg_if_apply {
($m:meta, $($it:item)*) => {
$(#[$m] $it)*
}
}
53 changes: 53 additions & 0 deletions src/ca-loader/src/sys/macos.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
extern crate security_framework as sf;

use super::super::CertItem;
use self::sf::item::{ItemClass, ItemSearchOptions, Reference, SearchResult};
use self::sf::keychain::SecKeychain;
use self::sf::os::macos::keychain::SecKeychainExt;
use std::i32;
use std::result::Result;

pub struct CertBundle {
rv: Vec<SearchResult>
}

pub struct CertIter {
it: Box<Iterator<Item=SearchResult>>
}

impl IntoIterator for CertBundle {
type Item = CertItem;
type IntoIter = CertIter;

fn into_iter(self) -> Self::IntoIter {
CertIter { it: Box::new(self.rv.into_iter()) }
}
}

impl Iterator for CertIter {
type Item = CertItem;

fn next(&mut self) -> Option<CertItem> {
if let Some(res) = self.it.next() {
if let Some(ref rref) = res.reference {
match rref {
&Reference::Certificate(ref cert) => return Some(CertItem::Blob(cert.to_der())),
_ => ()
}
}
return self.next();
}
None
}
}

impl CertBundle {
pub fn new() -> Result<CertBundle, ()> {
let root_kc = try!(SecKeychain::open("/System/Library/Keychains/SystemRootCertificates.keychain").map_err(|_| ()));
let chains = [ root_kc ];
let mut opts = ItemSearchOptions::new();
let opts = opts.keychains(&chains).class(ItemClass::Certificate).load_refs(true).limit(i32::MAX as i64);
let rv = try!(opts.search().map_err(|_| ()));
Ok(CertBundle { rv: rv })
}
}
14 changes: 14 additions & 0 deletions src/ca-loader/src/sys/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
cfg_if! {
if #[cfg(windows)] {
mod windows;
pub use self::windows::CertBundle;
} else if #[cfg(target_os = "macos")] {
mod macos;
pub use self::macos::CertBundle;
} else if #[cfg(unix)] {
mod unix;
pub use self::unix::CertBundle;
} else {
// Unknown
}
}
140 changes: 140 additions & 0 deletions src/ca-loader/src/sys/unix.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
extern crate libc;

use std::ffi::CStr;
use std::fs;
use std::mem;
use std::result::Result;
use super::super::CertItem;

cfg_if! {
if #[cfg(any(target_os = "android", target_os = "solaris"))] {
use std::fs::{read_dir, ReadDir};

pub struct CertBundle(&'static str);

pub struct CertIter(&'static str, Option<ReadDir>);

impl IntoIterator for CertBundle {
type Item = CertItem;
type IntoIter = CertIter;

fn into_iter(self) -> Self::IntoIter {
if let Ok(dir) = read_dir(self.0) {
CertIter(self.0, Some(dir))
} else {
CertIter(self.0, None)
}
}
}

impl Iterator for CertIter {
type Item = CertItem;

fn next(&mut self) -> Option<Self::Item> {
match self.1 {
None => return None,
Some(ref mut dir) => {
match dir.next() {
None => return None,
Some(Err(_)) => return None,
Some(Ok(ref de)) => {
if let Ok(ftyp) = de.file_type() {
if !ftyp.is_dir() {
if let Some(s) = de.file_name().to_str() {
let mut full_name = String::from(self.0);
full_name.push('/');
full_name.push_str(s);
return Some(CertItem::File(full_name));
}
}
}
}
}
}
}
self.next()
}
}

impl CertBundle {
pub fn new() -> Result<CertBundle, ()> {
Ok(CertBundle(try!(sys_path())))
}
}
} else {
use std::option;

pub struct CertBundle(Option<CertItem>);

impl IntoIterator for CertBundle {
type Item = CertItem;
type IntoIter = option::IntoIter<CertItem>;

fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}

impl CertBundle {
pub fn new() -> Result<CertBundle, ()> {
Ok(CertBundle(Some(CertItem::File(try!(sys_path()).to_string()))))
}
}
}
}

pub fn sys_path() -> Result<&'static str, ()> {
// Why use mem::uninitialized()? If we didn't, we'd need a bunch of
// #cfg's for OS variants, since the components of struct utsname are
// fixed-size char arrays (so no generic initializers), and that size
// is different across OSs. Furthermore, uname() doesn't care about
// the contents of struct utsname on input, and will fill it with
// properly NUL-terminated strings on successful return.
unsafe {
let mut uts = mem::uninitialized::<libc::utsname>();

if libc::uname(&mut uts) < 0 {
return Err(());
}
let sysname = try!(CStr::from_ptr(uts.sysname.as_ptr()).to_str().map_err(|_| ()));
let release = try!(CStr::from_ptr(uts.release.as_ptr()).to_str().map_err(|_| ()));
let path = match sysname {
"FreeBSD" | "OpenBSD" => "/etc/ssl/cert.pem",
"NetBSD" => "/etc/ssl/certs",
"Linux" => linux_distro_guess_ca_path(),
"SunOS" => {
let major = release.split('.').take(1).collect::<String>();
let major = major.parse::<u32>().unwrap_or(5);
let minor = release.split('.').skip(1).take(1).collect::<String>();
let minor = minor.parse::<u32>().unwrap_or(10);
if major < 5 || (major == 5 && minor < 11) {
"/opt/csw/share/cacertificates/mozilla"
} else {
"/etc/certs/CA"
}
}
_ => unimplemented!()
};
Ok(path)
}
}

cfg_if! {
if #[cfg(target_os = "android")] {
fn linux_distro_guess_ca_path() -> &'static str {
"/system/etc/security/cacerts"
}
} else {
fn linux_distro_guess_ca_path() -> &'static str {
if let Ok(_debian) = fs::metadata("/etc/debian_version") {
"/etc/ssl/certs/ca-certificates.crt"
} else if let Ok(_rh) = fs::metadata("/etc/redhat-release") {
"/etc/pki/tls/certs/ca-bundle.crt"
} else if let Ok(_suse) = fs::metadata("/etc/SuSE-release") {
"/etc/ssl/ca-bundle.pem"
} else { // fallback
"/etc/pki/tls/cacert.pem"
}
}
}
}
79 changes: 79 additions & 0 deletions src/ca-loader/src/sys/windows.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
extern crate crypt32;
extern crate winapi;

use super::super::CertItem;
use std::ffi::CString;
use std::ptr;
use std::result::Result;
use std::slice::from_raw_parts;

pub struct CertBundle {
store: winapi::HCERTSTORE,
ctx_p: winapi::PCCERT_CONTEXT
}

pub struct CertIter {
bundle: CertBundle
}

impl IntoIterator for CertBundle {
type Item = CertItem;
type IntoIter = CertIter;

fn into_iter(self) -> Self::IntoIter {
CertIter { bundle: self }
}
}

impl Iterator for CertIter {
type Item = CertItem;

fn next(&mut self) -> Option<CertItem> {
if self.bundle.ctx_p.is_null() {
return None;
}
unsafe {
let ctx = *self.bundle.ctx_p;
let enc_slice = from_raw_parts(
ctx.pbCertEncoded as *const u8,
ctx.cbCertEncoded as usize);
let mut blob = Vec::with_capacity(ctx.cbCertEncoded as usize);
blob.extend_from_slice(enc_slice);
self.bundle.ctx_p = crypt32::CertEnumCertificatesInStore(
self.bundle.store,
self.bundle.ctx_p);
Some(CertItem::Blob(blob))
}
}
}

impl CertBundle {
pub fn new() -> Result<CertBundle, ()> {
unsafe {
let store = crypt32::CertOpenSystemStoreA(
0,
CString::new("Root").unwrap().as_ptr() as winapi::LPCSTR);
if store.is_null() {
return Err(());
}
let ctx_p = crypt32::CertEnumCertificatesInStore(
store,
ptr::null());
Ok(CertBundle {
store: store,
ctx_p: ctx_p
})
}
}
}

impl Drop for CertBundle {
fn drop(&mut self) {
unsafe {
if !self.ctx_p.is_null() {
crypt32::CertFreeCertificateContext(self.ctx_p);
}
crypt32::CertCloseStore(self.store, 0);
}
}
}
7 changes: 6 additions & 1 deletion src/download/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ default = ["hyper-backend"]

curl-backend = ["curl"]
hyper-backend = ["hyper", "native-tls", "openssl-sys"]
rustls-backend = ["hyper", "rustls", "lazy_static"]
rustls-backend = ["hyper", "rustls", "lazy_static", "ca-loader"]

[dependencies]
error-chain = "0.2.1"
Expand All @@ -35,3 +35,8 @@ openssl-sys = { version = "0.7.11", optional = true }
[dependencies.rustls]
git = "https://github.com/ctz/rustls.git"
optional = true

[dependencies.ca-loader]
path = "../ca-loader"
version = "0.1.0"
optional = true
Loading

0 comments on commit fe1cc85

Please sign in to comment.