Skip to content
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 <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