Skip to content

Commit

Permalink
Remove dependency on rustls
Browse files Browse the repository at this point in the history
  • Loading branch information
djc committed Jul 7, 2021
1 parent 72e1bd8 commit 6116ef5
Show file tree
Hide file tree
Showing 8 changed files with 57 additions and 80 deletions.
10 changes: 2 additions & 8 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,14 @@ repository = "https://github.com/ctz/rustls-native-certs"
categories = ["network-programming", "cryptography"]

[dependencies]
rustls = { version = "0.19.0", optional = true }
rustls-pemfile = "0.2.1"

[dev-dependencies]
webpki = "0.21"
webpki-roots = "0.21.1"
ring = "0.16.5"
untrusted = "0.7.0"

[features]
default = ["rustls"]

[[example]]
name = "google"
required-features = ["rustls"]
rustls = "0.19.1"

[target.'cfg(windows)'.dependencies]
schannel = "0.1.15"
Expand Down
8 changes: 6 additions & 2 deletions examples/google.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,13 @@ use webpki;
use rustls::Session;

fn main() {
let mut roots = rustls::RootCertStore::empty();
for cert in rustls_native_certs::load_native_certs().expect("could not load platform certs") {
roots.add(&rustls::Certificate(cert.0)).unwrap();
}

let mut config = rustls::ClientConfig::new();
config.root_store = rustls_native_certs::load_native_certs()
.expect("could not load platform certs");
config.root_store = roots;

let dns_name = webpki::DNSNameRef::try_from_ascii_str("google.com")
.unwrap();
Expand Down
18 changes: 4 additions & 14 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,19 +25,7 @@ mod macos;
#[cfg(target_os = "macos")]
use macos as platform;

#[cfg(feature = "rustls")]
mod rustls;

use std::io::Error;
use std::io::BufRead;

#[cfg(feature = "rustls")]
pub use crate::rustls::{load_native_certs, PartialResult};

pub trait RootStoreBuilder {
fn load_der(&mut self, der: Vec<u8>) -> Result<(), Error>;
fn load_pem_file(&mut self, rd: &mut dyn BufRead) -> Result<(), Error>;
}

/// Loads root certificates found in the platform's native certificate
/// store, executing callbacks on the provided builder.
Expand All @@ -47,6 +35,8 @@ pub trait RootStoreBuilder {
/// This function can be expensive: on some platforms it involves loading
/// and parsing a ~300KB disk file. It's therefore prudent to call
/// this sparingly.
pub fn build_native_certs<B: RootStoreBuilder>(builder: &mut B) -> Result<(), Error> {
platform::build_native_certs(builder)
pub fn load_native_certs() -> Result<Vec<Certificate>, Error> {
platform::load_native_certs()
}

pub struct Certificate(pub Vec<u8>);
27 changes: 9 additions & 18 deletions src/macos.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
use crate::Certificate;

use security_framework::trust_settings::{
Domain,
TrustSettings,
TrustSettingsForCertificate
};

use std::io::{Error, ErrorKind};
use std::collections::HashMap;

use crate::RootStoreBuilder;

pub fn build_native_certs<B: RootStoreBuilder>(builder: &mut B) -> Result<(), Error> {
pub fn load_native_certs() -> Result<Vec<Certificate>, Error> {
// The various domains are designed to interact like this:
//
// "Per-user Trust Settings override locally administered
Expand Down Expand Up @@ -45,26 +46,16 @@ pub fn build_native_certs<B: RootStoreBuilder>(builder: &mut B) -> Result<(), Er
}
}

let mut first_error = None;
let mut certs = Vec::new();

// Now we have all the certificates and an idea of whether
// to use them.
for (der, trusted) in all_certs.drain() {
match trusted {
TrustSettingsForCertificate::TrustRoot |
TrustSettingsForCertificate::TrustAsRoot => {
if let Err(err) = builder.load_der(der) {
first_error = first_error
.or_else(|| Some(Error::new(ErrorKind::InvalidData, err)));
}
},
_ => {} // discard
use TrustSettingsForCertificate::*;
if let TrustRoot | TrustAsRoot = trusted {
certs.push(Certificate(der));
}
}

if let Some(err) = first_error {
Err(err)
} else {
Ok(())
}
Ok(certs)
}
25 changes: 15 additions & 10 deletions src/unix.rs
Original file line number Diff line number Diff line change
@@ -1,34 +1,39 @@
use crate::RootStoreBuilder;
use crate::Certificate;

use std::io::{Error, ErrorKind};
use std::io::BufReader;
use std::fs::File;
use std::path::Path;

fn load_file(builder: &mut impl RootStoreBuilder, path: &Path) -> Result<(), Error> {
fn load_file(certs: &mut Vec<Certificate>, path: &Path) -> Result<(), Error> {
let f = File::open(&path)?;
let mut f = BufReader::new(f);
if builder.load_pem_file(&mut f).is_err() {
Err(Error::new(ErrorKind::InvalidData,
format!("Could not load PEM file {:?}", path)))
} else {
Ok(())
match rustls_pemfile::certs(&mut f) {
Ok(contents) => {
certs.extend(contents.into_iter().map(Certificate));
Ok(())
}
Err(_) => Err(Error::new(
ErrorKind::InvalidData,
format!("Could not load PEM file {:?}", path),
)),
}
}

pub fn build_native_certs<B: RootStoreBuilder>(builder: &mut B) -> Result<(), Error> {
pub fn load_native_certs() -> Result<Vec<Certificate>, Error> {
let likely_locations = openssl_probe::probe();
let mut first_error = None;
let mut certs = Vec::new();

if let Some(file) = likely_locations.cert_file {
if let Err(err) = load_file(builder, &file) {
if let Err(err) = load_file(&mut certs, &file) {
first_error = first_error.or(Some(err));
}
}

if let Some(err) = first_error {
Err(err)
} else {
Ok(())
Ok(certs)
}
}
23 changes: 7 additions & 16 deletions src/windows.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::RootStoreBuilder;
use crate::Certificate;

use std::io::{Error, ErrorKind};
use std::io::Error;

static PKIX_SERVER_AUTH: &str = "1.3.6.1.5.5.7.3.1";

Expand All @@ -13,25 +13,16 @@ fn usable_for_rustls(uses: schannel::cert_context::ValidUses) -> bool {
}
}

pub fn build_native_certs<B: RootStoreBuilder>(builder: &mut B) -> Result<(), Error> {
let mut first_error = None;
pub fn load_native_certs() -> Result<Vec<Certificate>, Error> {
let mut certs = Vec::new();

let current_user_store = schannel::cert_store::CertStore::open_current_user("ROOT")?;

for cert in current_user_store.certs() {
if !usable_for_rustls(cert.valid_uses().unwrap()) {
continue;
}

if let Err(err) = builder.load_der(cert.to_der().to_vec()) {
first_error = first_error
.or_else(|| Some(Error::new(ErrorKind::InvalidData, err)));
if usable_for_rustls(cert.valid_uses().unwrap()) {
certs.push(Certificate(cert.to_der().to_vec()));
}
}

if let Some(err) = first_error {
Err(err)
} else {
Ok(())
}
Ok(certs)
}
16 changes: 8 additions & 8 deletions tests/compare_mozilla.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@
//! as expressed by the `webpki-roots` crate.
//!
//! This is, obviously, quite a heuristic test.
#![cfg(feature = "rustls")]

use std::collections::HashMap;
use ring::io::der;
use untrusted;
Expand Down Expand Up @@ -75,8 +73,8 @@ fn test_does_not_have_many_roots_unknown_by_mozilla() {

let mut missing_in_moz_roots = 0;

for cert in &native.roots {
let cert = cert.to_trust_anchor();
for cert in &native {
let cert = webpki::trust_anchor_util::cert_der_as_trust_anchor(&cert.0).unwrap();
if let Some(moz) = mozilla.get(cert.spki) {
assert_eq!(cert.subject, moz.subject,
"subjects differ for public key");
Expand Down Expand Up @@ -106,8 +104,9 @@ fn test_contains_most_roots_known_by_mozilla() {
.unwrap();

let mut native_map = HashMap::new();
for anchor in &native.roots {
native_map.insert(anchor.to_trust_anchor().spki.to_vec(), anchor);
for anchor in &native {
let cert = webpki::trust_anchor_util::cert_der_as_trust_anchor(&anchor.0).unwrap();
native_map.insert(cert.spki.to_vec(), anchor);
}

let mut missing_in_native_roots = 0;
Expand Down Expand Up @@ -139,7 +138,8 @@ fn util_list_certs() {
let native = rustls_native_certs::load_native_certs()
.unwrap();

for (i, cert) in native.roots.iter().enumerate() {
println!("cert[{}] = {}", i, stringify_x500name(cert.to_trust_anchor().subject));
for (i, cert) in native.iter().enumerate() {
let cert = webpki::trust_anchor_util::cert_der_as_trust_anchor(&cert.0).unwrap();
println!("cert[{}] = {}", i, stringify_x500name(cert.subject));
}
}
10 changes: 6 additions & 4 deletions tests/smoketests.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
#![cfg(feature = "rustls")]

use std::sync::Arc;

use std::net::TcpStream;
Expand All @@ -10,9 +8,13 @@ use webpki;
use rustls_native_certs;

fn check_site(domain: &str) {
let mut roots = rustls::RootCertStore::empty();
for cert in rustls_native_certs::load_native_certs().unwrap() {
roots.add(&rustls::Certificate(cert.0)).unwrap();
}

let mut config = rustls::ClientConfig::new();
config.root_store = rustls_native_certs::load_native_certs()
.unwrap();
config.root_store = roots;

let dns_name = webpki::DNSNameRef::try_from_ascii_str(domain)
.unwrap();
Expand Down

0 comments on commit 6116ef5

Please sign in to comment.