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

Remove dependency on rustls #26

Merged
merged 4 commits into from
Oct 9, 2021
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
2 changes: 1 addition & 1 deletion .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
- stable
- beta
- nightly
- 1.39.0
- 1.50.0
os: [ubuntu-18.04]
# but only stable on macos/windows (slower platforms)
include:
Expand Down
14 changes: 4 additions & 10 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"
webpki = "0.22"
webpki-roots = "0.22"
ring = "0.16.5"
untrusted = "0.7.0"

[features]
default = ["rustls"]

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

[target.'cfg(windows)'.dependencies]
schannel = "0.1.15"
Expand Down
67 changes: 37 additions & 30 deletions examples/google.rs
Original file line number Diff line number Diff line change
@@ -1,39 +1,46 @@
use std::convert::TryInto;
use std::sync::Arc;

use std::io::{stdout, Read, Write};
use std::net::TcpStream;
use std::io::{Read, Write, stdout};

use rustls;
use rustls_native_certs;
use webpki;

use rustls::Session;

fn main() {
let mut config = rustls::ClientConfig::new();
config.root_store = rustls_native_certs::load_native_certs()
.expect("could not load platform certs");
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 config = rustls::ClientConfig::builder()
.with_safe_defaults()
.with_root_certificates(roots)
.with_no_client_auth();

let dns_name = webpki::DNSNameRef::try_from_ascii_str("google.com")
.unwrap();
let mut sess = rustls::ClientSession::new(&Arc::new(config), dns_name);
let mut sock = TcpStream::connect("google.com:443")
.expect("cannot connect");
let mut tls = rustls::Stream::new(&mut sess, &mut sock);
tls.write(concat!("GET / HTTP/1.1\r\n",
"Host: google.com\r\n",
"Connection: close\r\n",
"Accept-Encoding: identity\r\n",
"\r\n")
.as_bytes())
.expect("write failed");
let ciphersuite = tls.sess.get_negotiated_ciphersuite()
let mut conn =
rustls::ClientConnection::new(Arc::new(config), "google.com".try_into().unwrap()).unwrap();
let mut sock = TcpStream::connect("google.com:443").expect("cannot connect");
let mut tls = rustls::Stream::new(&mut conn, &mut sock);
tls.write_all(
concat!(
"GET / HTTP/1.1\r\n",
"Host: google.com\r\n",
"Connection: close\r\n",
"Accept-Encoding: identity\r\n",
"\r\n"
)
.as_bytes(),
)
.expect("write failed");
let ciphersuite = tls
.conn
.negotiated_cipher_suite()
.expect("tls handshake failed");
writeln!(&mut std::io::stderr(), "Current ciphersuite: {:?}", ciphersuite.suite)
.unwrap();
writeln!(
&mut std::io::stderr(),
"Current ciphersuite: {:?}",
ciphersuite.suite()
)
.unwrap();
let mut plaintext = Vec::new();
tls.read_to_end(&mut plaintext)
.unwrap();
stdout().write_all(&plaintext)
.unwrap();
tls.read_to_end(&mut plaintext).unwrap();
stdout().write_all(&plaintext).unwrap();
}
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)
}
49 changes: 25 additions & 24 deletions tests/compare_mozilla.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,10 @@
//! 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;
use webpki::TrustAnchor;

fn stringify_x500name(subject: &[u8]) -> String {
let mut parts = vec![];
Expand All @@ -19,30 +18,30 @@ fn stringify_x500name(subject: &[u8]) -> String {
.unwrap();
assert!(tag == 0x31); // sequence, constructed, context=1

let mut inner = untrusted::Reader::new(contents.into());
let mut inner = untrusted::Reader::new(contents);
let pair = der::expect_tag_and_get_value(&mut inner, der::Tag::Sequence)
.unwrap();

let mut pair = untrusted::Reader::new(pair.into());
let mut pair = untrusted::Reader::new(pair);
let oid = der::expect_tag_and_get_value(&mut pair, der::Tag::OID)
.unwrap();
let (valuety, value) = der::read_tag_and_get_value(&mut pair)
.unwrap();

let name = match oid.as_slice_less_safe() {
&[0x55, 0x04, 0x03] => "CN",
&[0x55, 0x04, 0x05] => "serialNumber",
&[0x55, 0x04, 0x06] => "C",
&[0x55, 0x04, 0x07] => "L",
&[0x55, 0x04, 0x08] => "ST",
&[0x55, 0x04, 0x09] => "STREET",
&[0x55, 0x04, 0x0a] => "O",
&[0x55, 0x04, 0x0b] => "OU",
&[0x55, 0x04, 0x11] => "postalCode",
&[0x55, 0x04, 0x61] => "organizationIdentifier",
&[0x09, 0x92, 0x26, 0x89, 0x93, 0xf2, 0x2c, 0x64, 0x01, 0x19] => "domainComponent",
&[0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x01] => "emailAddress",
_ => panic!("unhandled x500 attr {:?}", oid)
[0x55, 0x04, 0x03] => "CN",
[0x55, 0x04, 0x05] => "serialNumber",
[0x55, 0x04, 0x06] => "C",
[0x55, 0x04, 0x07] => "L",
[0x55, 0x04, 0x08] => "ST",
[0x55, 0x04, 0x09] => "STREET",
[0x55, 0x04, 0x0a] => "O",
[0x55, 0x04, 0x0b] => "OU",
[0x55, 0x04, 0x11] => "postalCode",
[0x55, 0x04, 0x61] => "organizationIdentifier",
[0x09, 0x92, 0x26, 0x89, 0x93, 0xf2, 0x2c, 0x64, 0x01, 0x19] => "domainComponent",
[0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x01] => "emailAddress",
_ => panic!("unhandled x500 attr {:?}", oid),
};

let str_value = match valuety {
Expand Down Expand Up @@ -75,8 +74,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 = TrustAnchor::try_from_cert_der(&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 +105,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 = TrustAnchor::try_from_cert_der(&anchor.0).unwrap();
native_map.insert(cert.spki.to_vec(), anchor);
}

let mut missing_in_native_roots = 0;
Expand Down Expand Up @@ -139,7 +139,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 = TrustAnchor::try_from_cert_der(&cert.0).unwrap();
println!("cert[{}] = {}", i, stringify_x500name(cert.subject));
}
}
Loading