Skip to content

Commit fe1cc85

Browse files
authored
Merge pull request #585 from inejge/cacert
Improve rustls CA certificate loading
2 parents 7d7fed2 + d54a9d5 commit fe1cc85

File tree

10 files changed

+395
-10
lines changed

10 files changed

+395
-10
lines changed

Cargo.lock

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/ca-loader/Cargo.toml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
[package]
2+
name = "ca-loader"
3+
version = "0.1.0"
4+
authors = [ "Ivan Nejgebauer <[email protected]>" ]
5+
6+
[dependencies]
7+
libc = "0.2"
8+
9+
[target."cfg(windows)".dependencies]
10+
winapi = "0.2.8"
11+
crypt32-sys = "0.2"
12+
13+
[target.'cfg(target_os = "macos")'.dependencies]
14+
security-framework = "0.1.5"

src/ca-loader/src/lib.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#[macro_use]
2+
mod macros;
3+
mod sys;
4+
5+
pub use self::sys::CertBundle;
6+
7+
pub enum CertItem {
8+
File(String),
9+
Blob(Vec<u8>)
10+
}

src/ca-loader/src/macros.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// Taken from the libc crate, see <https://github.com/rust-lang/libc> for
2+
// authorship and copyright information.
3+
4+
// A macro for defining #[cfg] if-else statements.
5+
//
6+
// This is similar to the `if/elif` C preprocessor macro by allowing definition
7+
// of a cascade of `#[cfg]` cases, emitting the implementation which matches
8+
// first.
9+
//
10+
// This allows you to conveniently provide a long list #[cfg]'d blocks of code
11+
// without having to rewrite each clause multiple times.
12+
macro_rules! cfg_if {
13+
($(
14+
if #[cfg($($meta:meta),*)] { $($it:item)* }
15+
) else * else {
16+
$($it2:item)*
17+
}) => {
18+
__cfg_if_items! {
19+
() ;
20+
$( ( ($($meta),*) ($($it)*) ), )*
21+
( () ($($it2)*) ),
22+
}
23+
}
24+
}
25+
26+
macro_rules! __cfg_if_items {
27+
(($($not:meta,)*) ; ) => {};
28+
(($($not:meta,)*) ; ( ($($m:meta),*) ($($it:item)*) ), $($rest:tt)*) => {
29+
__cfg_if_apply! { cfg(all(not(any($($not),*)), $($m,)*)), $($it)* }
30+
__cfg_if_items! { ($($not,)* $($m,)*) ; $($rest)* }
31+
}
32+
}
33+
34+
macro_rules! __cfg_if_apply {
35+
($m:meta, $($it:item)*) => {
36+
$(#[$m] $it)*
37+
}
38+
}

src/ca-loader/src/sys/macos.rs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
extern crate security_framework as sf;
2+
3+
use super::super::CertItem;
4+
use self::sf::item::{ItemClass, ItemSearchOptions, Reference, SearchResult};
5+
use self::sf::keychain::SecKeychain;
6+
use self::sf::os::macos::keychain::SecKeychainExt;
7+
use std::i32;
8+
use std::result::Result;
9+
10+
pub struct CertBundle {
11+
rv: Vec<SearchResult>
12+
}
13+
14+
pub struct CertIter {
15+
it: Box<Iterator<Item=SearchResult>>
16+
}
17+
18+
impl IntoIterator for CertBundle {
19+
type Item = CertItem;
20+
type IntoIter = CertIter;
21+
22+
fn into_iter(self) -> Self::IntoIter {
23+
CertIter { it: Box::new(self.rv.into_iter()) }
24+
}
25+
}
26+
27+
impl Iterator for CertIter {
28+
type Item = CertItem;
29+
30+
fn next(&mut self) -> Option<CertItem> {
31+
if let Some(res) = self.it.next() {
32+
if let Some(ref rref) = res.reference {
33+
match rref {
34+
&Reference::Certificate(ref cert) => return Some(CertItem::Blob(cert.to_der())),
35+
_ => ()
36+
}
37+
}
38+
return self.next();
39+
}
40+
None
41+
}
42+
}
43+
44+
impl CertBundle {
45+
pub fn new() -> Result<CertBundle, ()> {
46+
let root_kc = try!(SecKeychain::open("/System/Library/Keychains/SystemRootCertificates.keychain").map_err(|_| ()));
47+
let chains = [ root_kc ];
48+
let mut opts = ItemSearchOptions::new();
49+
let opts = opts.keychains(&chains).class(ItemClass::Certificate).load_refs(true).limit(i32::MAX as i64);
50+
let rv = try!(opts.search().map_err(|_| ()));
51+
Ok(CertBundle { rv: rv })
52+
}
53+
}

src/ca-loader/src/sys/mod.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
cfg_if! {
2+
if #[cfg(windows)] {
3+
mod windows;
4+
pub use self::windows::CertBundle;
5+
} else if #[cfg(target_os = "macos")] {
6+
mod macos;
7+
pub use self::macos::CertBundle;
8+
} else if #[cfg(unix)] {
9+
mod unix;
10+
pub use self::unix::CertBundle;
11+
} else {
12+
// Unknown
13+
}
14+
}

src/ca-loader/src/sys/unix.rs

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
extern crate libc;
2+
3+
use std::ffi::CStr;
4+
use std::fs;
5+
use std::mem;
6+
use std::result::Result;
7+
use super::super::CertItem;
8+
9+
cfg_if! {
10+
if #[cfg(any(target_os = "android", target_os = "solaris"))] {
11+
use std::fs::{read_dir, ReadDir};
12+
13+
pub struct CertBundle(&'static str);
14+
15+
pub struct CertIter(&'static str, Option<ReadDir>);
16+
17+
impl IntoIterator for CertBundle {
18+
type Item = CertItem;
19+
type IntoIter = CertIter;
20+
21+
fn into_iter(self) -> Self::IntoIter {
22+
if let Ok(dir) = read_dir(self.0) {
23+
CertIter(self.0, Some(dir))
24+
} else {
25+
CertIter(self.0, None)
26+
}
27+
}
28+
}
29+
30+
impl Iterator for CertIter {
31+
type Item = CertItem;
32+
33+
fn next(&mut self) -> Option<Self::Item> {
34+
match self.1 {
35+
None => return None,
36+
Some(ref mut dir) => {
37+
match dir.next() {
38+
None => return None,
39+
Some(Err(_)) => return None,
40+
Some(Ok(ref de)) => {
41+
if let Ok(ftyp) = de.file_type() {
42+
if !ftyp.is_dir() {
43+
if let Some(s) = de.file_name().to_str() {
44+
let mut full_name = String::from(self.0);
45+
full_name.push('/');
46+
full_name.push_str(s);
47+
return Some(CertItem::File(full_name));
48+
}
49+
}
50+
}
51+
}
52+
}
53+
}
54+
}
55+
self.next()
56+
}
57+
}
58+
59+
impl CertBundle {
60+
pub fn new() -> Result<CertBundle, ()> {
61+
Ok(CertBundle(try!(sys_path())))
62+
}
63+
}
64+
} else {
65+
use std::option;
66+
67+
pub struct CertBundle(Option<CertItem>);
68+
69+
impl IntoIterator for CertBundle {
70+
type Item = CertItem;
71+
type IntoIter = option::IntoIter<CertItem>;
72+
73+
fn into_iter(self) -> Self::IntoIter {
74+
self.0.into_iter()
75+
}
76+
}
77+
78+
impl CertBundle {
79+
pub fn new() -> Result<CertBundle, ()> {
80+
Ok(CertBundle(Some(CertItem::File(try!(sys_path()).to_string()))))
81+
}
82+
}
83+
}
84+
}
85+
86+
pub fn sys_path() -> Result<&'static str, ()> {
87+
// Why use mem::uninitialized()? If we didn't, we'd need a bunch of
88+
// #cfg's for OS variants, since the components of struct utsname are
89+
// fixed-size char arrays (so no generic initializers), and that size
90+
// is different across OSs. Furthermore, uname() doesn't care about
91+
// the contents of struct utsname on input, and will fill it with
92+
// properly NUL-terminated strings on successful return.
93+
unsafe {
94+
let mut uts = mem::uninitialized::<libc::utsname>();
95+
96+
if libc::uname(&mut uts) < 0 {
97+
return Err(());
98+
}
99+
let sysname = try!(CStr::from_ptr(uts.sysname.as_ptr()).to_str().map_err(|_| ()));
100+
let release = try!(CStr::from_ptr(uts.release.as_ptr()).to_str().map_err(|_| ()));
101+
let path = match sysname {
102+
"FreeBSD" | "OpenBSD" => "/etc/ssl/cert.pem",
103+
"NetBSD" => "/etc/ssl/certs",
104+
"Linux" => linux_distro_guess_ca_path(),
105+
"SunOS" => {
106+
let major = release.split('.').take(1).collect::<String>();
107+
let major = major.parse::<u32>().unwrap_or(5);
108+
let minor = release.split('.').skip(1).take(1).collect::<String>();
109+
let minor = minor.parse::<u32>().unwrap_or(10);
110+
if major < 5 || (major == 5 && minor < 11) {
111+
"/opt/csw/share/cacertificates/mozilla"
112+
} else {
113+
"/etc/certs/CA"
114+
}
115+
}
116+
_ => unimplemented!()
117+
};
118+
Ok(path)
119+
}
120+
}
121+
122+
cfg_if! {
123+
if #[cfg(target_os = "android")] {
124+
fn linux_distro_guess_ca_path() -> &'static str {
125+
"/system/etc/security/cacerts"
126+
}
127+
} else {
128+
fn linux_distro_guess_ca_path() -> &'static str {
129+
if let Ok(_debian) = fs::metadata("/etc/debian_version") {
130+
"/etc/ssl/certs/ca-certificates.crt"
131+
} else if let Ok(_rh) = fs::metadata("/etc/redhat-release") {
132+
"/etc/pki/tls/certs/ca-bundle.crt"
133+
} else if let Ok(_suse) = fs::metadata("/etc/SuSE-release") {
134+
"/etc/ssl/ca-bundle.pem"
135+
} else { // fallback
136+
"/etc/pki/tls/cacert.pem"
137+
}
138+
}
139+
}
140+
}

src/ca-loader/src/sys/windows.rs

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
extern crate crypt32;
2+
extern crate winapi;
3+
4+
use super::super::CertItem;
5+
use std::ffi::CString;
6+
use std::ptr;
7+
use std::result::Result;
8+
use std::slice::from_raw_parts;
9+
10+
pub struct CertBundle {
11+
store: winapi::HCERTSTORE,
12+
ctx_p: winapi::PCCERT_CONTEXT
13+
}
14+
15+
pub struct CertIter {
16+
bundle: CertBundle
17+
}
18+
19+
impl IntoIterator for CertBundle {
20+
type Item = CertItem;
21+
type IntoIter = CertIter;
22+
23+
fn into_iter(self) -> Self::IntoIter {
24+
CertIter { bundle: self }
25+
}
26+
}
27+
28+
impl Iterator for CertIter {
29+
type Item = CertItem;
30+
31+
fn next(&mut self) -> Option<CertItem> {
32+
if self.bundle.ctx_p.is_null() {
33+
return None;
34+
}
35+
unsafe {
36+
let ctx = *self.bundle.ctx_p;
37+
let enc_slice = from_raw_parts(
38+
ctx.pbCertEncoded as *const u8,
39+
ctx.cbCertEncoded as usize);
40+
let mut blob = Vec::with_capacity(ctx.cbCertEncoded as usize);
41+
blob.extend_from_slice(enc_slice);
42+
self.bundle.ctx_p = crypt32::CertEnumCertificatesInStore(
43+
self.bundle.store,
44+
self.bundle.ctx_p);
45+
Some(CertItem::Blob(blob))
46+
}
47+
}
48+
}
49+
50+
impl CertBundle {
51+
pub fn new() -> Result<CertBundle, ()> {
52+
unsafe {
53+
let store = crypt32::CertOpenSystemStoreA(
54+
0,
55+
CString::new("Root").unwrap().as_ptr() as winapi::LPCSTR);
56+
if store.is_null() {
57+
return Err(());
58+
}
59+
let ctx_p = crypt32::CertEnumCertificatesInStore(
60+
store,
61+
ptr::null());
62+
Ok(CertBundle {
63+
store: store,
64+
ctx_p: ctx_p
65+
})
66+
}
67+
}
68+
}
69+
70+
impl Drop for CertBundle {
71+
fn drop(&mut self) {
72+
unsafe {
73+
if !self.ctx_p.is_null() {
74+
crypt32::CertFreeCertificateContext(self.ctx_p);
75+
}
76+
crypt32::CertCloseStore(self.store, 0);
77+
}
78+
}
79+
}

src/download/Cargo.toml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ default = ["hyper-backend"]
1212

1313
curl-backend = ["curl"]
1414
hyper-backend = ["hyper", "native-tls", "openssl-sys"]
15-
rustls-backend = ["hyper", "rustls", "lazy_static"]
15+
rustls-backend = ["hyper", "rustls", "lazy_static", "ca-loader"]
1616

1717
[dependencies]
1818
error-chain = "0.2.1"
@@ -35,3 +35,8 @@ openssl-sys = { version = "0.7.11", optional = true }
3535
[dependencies.rustls]
3636
git = "https://github.com/ctz/rustls.git"
3737
optional = true
38+
39+
[dependencies.ca-loader]
40+
path = "../ca-loader"
41+
version = "0.1.0"
42+
optional = true

0 commit comments

Comments
 (0)