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

Switch to using the pki-types crate #72

Merged
merged 1 commit into from
Sep 6, 2023
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
9 changes: 5 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,16 @@ repository = "https://github.com/ctz/rustls-native-certs"
categories = ["network-programming", "cryptography"]

[dependencies]
rustls-pemfile = "1"
rustls-pemfile = "=2.0.0-alpha.0"
pki-types = { package = "rustls-pki-types", version = "0.1" }

[dev-dependencies]
ring = "0.16.5"
rustls = "0.21.0"
rustls-webpki = "0.101"
rustls = "=0.22.0-alpha.1"
rustls-webpki = "=0.102.0-alpha.1"
serial_test = "2"
untrusted = "0.7.0" # stick to the version ring depends on for now
webpki-roots = "0.25"
webpki-roots = "=0.26.0-alpha.0"
x509-parser = "0.15"

[target.'cfg(windows)'.dependencies]
Expand Down
12 changes: 5 additions & 7 deletions examples/google.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,16 @@
use std::convert::TryInto;
use std::sync::Arc;

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

use rustls::crypto::ring::Ring;

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();
roots.add(cert).unwrap();
}

let config = rustls::ClientConfig::builder()
let config = rustls::ClientConfig::<Ring>::builder()
.with_safe_defaults()
.with_root_certificates(roots)
.with_no_client_auth();
Expand Down
2 changes: 1 addition & 1 deletion examples/print-trust-anchors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use x509_parser::prelude::*;

fn main() -> Result<(), Box<dyn Error>> {
for cert in rustls_native_certs::load_native_certs()? {
match parse_x509_certificate(&cert.0) {
match parse_x509_certificate(cert.as_ref()) {
Ok((_, cert)) => println!("{}", cert.tbs_certificate.subject),
Err(e) => eprintln!("error parsing certificate: {}", e),
};
Expand Down
40 changes: 14 additions & 26 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,7 @@
//! ```no_run
//! 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();
//! roots.add(cert).unwrap();
//! }
//! ```

Expand All @@ -44,6 +42,8 @@ use std::io::BufReader;
use std::io::{Error, ErrorKind};
use std::path::{Path, PathBuf};

use pki_types::CertificateDer;

/// Load root certificates found in the platform's native certificate store.
///
/// If the SSL_CERT_FILE environment variable is set, certificates (in PEM
Expand All @@ -54,43 +54,31 @@ use std::path::{Path, PathBuf};
/// 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 load_native_certs() -> Result<Vec<Certificate>, Error> {
pub fn load_native_certs() -> Result<Vec<CertificateDer<'static>>, Error> {
load_certs_from_env().unwrap_or_else(platform::load_native_certs)
}

/// A newtype representing a single DER-encoded X.509 certificate encoded as a `Vec<u8>`.
pub struct Certificate(pub Vec<u8>);

impl AsRef<[u8]> for Certificate {
fn as_ref(&self) -> &[u8] {
&self.0
}
}

const ENV_CERT_FILE: &str = "SSL_CERT_FILE";

/// Returns None if SSL_CERT_FILE is not defined in the current environment.
///
/// If it is defined, it is always used, so it must be a path to a real
/// file from which certificates can be loaded successfully.
fn load_certs_from_env() -> Option<Result<Vec<Certificate>, Error>> {
fn load_certs_from_env() -> Option<Result<Vec<CertificateDer<'static>>, Error>> {
let cert_var_path = PathBuf::from(env::var_os(ENV_CERT_FILE)?);

Some(load_pem_certs(&cert_var_path))
}

fn load_pem_certs(path: &Path) -> Result<Vec<Certificate>, Error> {
fn load_pem_certs(path: &Path) -> Result<Vec<CertificateDer<'static>>, Error> {
let f = File::open(path)?;
let mut f = BufReader::new(f);

match rustls_pemfile::certs(&mut f) {
Ok(contents) => Ok(contents
.into_iter()
.map(Certificate)
.collect()),
Err(err) => Err(Error::new(
ErrorKind::InvalidData,
format!("Could not load PEM file {path:?}: {err}"),
)),
}
rustls_pemfile::certs(&mut f)
.collect::<Result<Vec<_>, _>>()
.map_err(|err| {
Error::new(
ErrorKind::InvalidData,
format!("could not load PEM file {path:?}: {err}"),
)
})
}
7 changes: 3 additions & 4 deletions src/macos.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
use crate::Certificate;

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

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

pub fn load_native_certs() -> Result<Vec<Certificate>, Error> {
pub fn load_native_certs() -> Result<Vec<CertificateDer<'static>>, Error> {
// The various domains are designed to interact like this:
//
// "Per-user Trust Settings override locally administered
Expand Down Expand Up @@ -50,7 +49,7 @@ pub fn load_native_certs() -> Result<Vec<Certificate>, Error> {
for (der, trusted) in all_certs.drain() {
use TrustSettingsForCertificate::*;
if let TrustRoot | TrustAsRoot = trusted {
certs.push(Certificate(der));
certs.push(CertificateDer::from(der));
}
}

Expand Down
5 changes: 3 additions & 2 deletions src/unix.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use crate::load_pem_certs;
use crate::Certificate;

use pki_types::CertificateDer;

use std::io::Error;

pub fn load_native_certs() -> Result<Vec<Certificate>, Error> {
pub fn load_native_certs() -> Result<Vec<CertificateDer<'static>>, Error> {
let likely_locations = openssl_probe::probe();

match likely_locations.cert_file {
Expand Down
6 changes: 3 additions & 3 deletions src/windows.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::Certificate;
use pki_types::CertificateDer;

use std::io::Error;

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

pub fn load_native_certs() -> Result<Vec<Certificate>, Error> {
pub fn load_native_certs() -> Result<Vec<CertificateDer<'static>>, 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()) && cert.is_time_valid().unwrap() {
certs.push(Certificate(cert.to_der().to_vec()));
certs.push(CertificateDer::from(cert.to_der().to_vec()));
}
}

Expand Down
31 changes: 18 additions & 13 deletions tests/compare_mozilla.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@
//! This is, obviously, quite a heuristic test.
use std::collections::HashMap;

use pki_types::Der;
use ring::io::der;
use webpki::TrustAnchor;
use webpki::extract_trust_anchor;

fn stringify_x500name(subject: &[u8]) -> String {
fn stringify_x500name(subject: &Der<'_>) -> String {
let mut parts = vec![];
let mut reader = untrusted::Reader::new(subject.into());
let mut reader = untrusted::Reader::new(subject.as_ref().into());

while !reader.at_end() {
let (tag, contents) = der::read_tag_and_get_value(&mut reader).unwrap();
Expand Down Expand Up @@ -57,19 +58,19 @@ fn test_does_not_have_many_roots_unknown_by_mozilla() {
let native = rustls_native_certs::load_native_certs().unwrap();
let mozilla = webpki_roots::TLS_SERVER_ROOTS
.iter()
.map(|ta| (ta.spki, ta))
.map(|ta| (ta.subject_public_key_info.as_ref(), ta))
.collect::<HashMap<_, _>>();

let mut missing_in_moz_roots = 0;

for cert in &native {
let cert = TrustAnchor::try_from_cert_der(&cert.0).unwrap();
if let Some(moz) = mozilla.get(cert.spki) {
let cert = extract_trust_anchor(&cert).unwrap();
if let Some(moz) = mozilla.get(cert.subject_public_key_info.as_ref()) {
assert_eq!(cert.subject, moz.subject, "subjects differ for public key");
} else {
println!(
"Native anchor {:?} is missing from mozilla set",
stringify_x500name(cert.subject)
stringify_x500name(&cert.subject)
);
missing_in_moz_roots += 1;
}
Expand Down Expand Up @@ -99,17 +100,21 @@ fn test_contains_most_roots_known_by_mozilla() {

let mut native_map = HashMap::new();
for anchor in &native {
let cert = TrustAnchor::try_from_cert_der(&anchor.0).unwrap();
native_map.insert(cert.spki.to_vec(), anchor);
let cert = extract_trust_anchor(&anchor).unwrap();
let spki = cert.subject_public_key_info.as_ref();
native_map.insert(spki.to_owned(), anchor);
}

let mut missing_in_native_roots = 0;
let mozilla = webpki_roots::TLS_SERVER_ROOTS;
for cert in mozilla {
if native_map.get(cert.spki).is_none() {
if native_map
.get(cert.subject_public_key_info.as_ref())
.is_none()
{
println!(
"Mozilla anchor {:?} is missing from native set",
stringify_x500name(cert.subject)
stringify_x500name(&cert.subject)
);
missing_in_native_roots += 1;
}
Expand Down Expand Up @@ -138,7 +143,7 @@ fn util_list_certs() {
let native = rustls_native_certs::load_native_certs().unwrap();

for (i, cert) in native.iter().enumerate() {
let cert = TrustAnchor::try_from_cert_der(&cert.0).unwrap();
println!("cert[{}] = {}", i, stringify_x500name(cert.subject));
let cert = extract_trust_anchor(&cert).unwrap();
println!("cert[{i}] = {}", stringify_x500name(&cert.subject));
}
}
15 changes: 5 additions & 10 deletions tests/smoketests.rs
Original file line number Diff line number Diff line change
@@ -1,26 +1,21 @@
use std::convert::TryInto;
use std::sync::Arc;

use std::panic;

use std::env;
use std::io::{Read, Write};
use std::net::TcpStream;
use std::path::PathBuf;
use std::sync::Arc;
use std::{env, panic};

use rustls::crypto::ring::Ring;
// #[serial] is used on all these tests to run them sequentially. If they're run in parallel,
// the global env var configuration in the env var test interferes with the others.
use serial_test::serial;

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();
roots.add(cert).unwrap();
}

let config = rustls::ClientConfig::builder()
let config = rustls::ClientConfig::<Ring>::builder()
.with_safe_defaults()
.with_root_certificates(roots)
.with_no_client_auth();
Expand Down