Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve rustls CA certificate loading #585

Merged
merged 2 commits into from
Jul 15, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 <[email protected]>" ]

[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