Skip to content
This repository has been archived by the owner on Jul 30, 2024. It is now read-only.

Commit

Permalink
Merge pull request #1 from robocorp/bogdan/show-cert-data
Browse files Browse the repository at this point in the history
Fork rustls-native-certs && show cert data
  • Loading branch information
Bogdan Condurache authored Nov 10, 2023
2 parents 3f306f2 + 58d0529 commit a213c10
Show file tree
Hide file tree
Showing 9 changed files with 216 additions and 12 deletions.
16 changes: 11 additions & 5 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ crate-type = ["cdylib"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
rustls-native-certs = "0.6.2"
rustls-native-certs = { path = "src/rustls-native-certs" }
x509-certificate = "0.21.0"

[dependencies.neon]
Expand Down
9 changes: 5 additions & 4 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@ fn load_native_certs(mut cx: FunctionContext) -> JsResult<JsString> {
certs
.into_iter()
.map(|cert| {
X509Certificate::from_der(&cert.0)
.expect("Could not import to DER")
.encode_pem()
.expect("could not encode to PEM")
let der = X509Certificate::from_der(&cert).expect("Could not import to DER");
let subject = der.subject_common_name().unwrap_or_else(|| String::new());
let issuer = der.issuer_common_name().unwrap_or_else(|| String::new());
let pem = der.encode_pem().expect("could not encode to PEM");
format!("# Subject: {subject}\n# Issuer: {issuer}\n{pem}\n")
})
.collect()
});
Expand Down
33 changes: 33 additions & 0 deletions src/rustls-native-certs/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
[package]
name = "rustls-native-certs"
version = "0.7.0-alpha.1"
edition = "2021"
rust-version = "1.60"
license = "Apache-2.0 OR ISC OR MIT"
readme = "README.md"
description = "rustls-native-certs allows rustls to use the platform native certificate store"
homepage = "https://github.com/rustls/rustls-native-certs"
repository = "https://github.com/rustls/rustls-native-certs"
categories = ["network-programming", "cryptography"]

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

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

[target.'cfg(windows)'.dependencies]
schannel = "0.1.15"

[target.'cfg(all(unix, not(target_os = "macos")))'.dependencies]
openssl-probe = "0.1.2"

[target.'cfg(target_os = "macos")'.dependencies]
security-framework = "2.0.0"
63 changes: 63 additions & 0 deletions src/rustls-native-certs/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
//! `rustls-native-certs` allows `rustls` to use the platform's native certificate
//! store when operating as a TLS client.
//!
//! It provides a single function [`load_native_certs()`], which returns a
//! collection of certificates found by reading the platform-native
//! certificate store.
//!
//! [`CertificateDer`] here is a marker newtype that denotes a DER-encoded
//! X.509 certificate encoded as a `Vec<u8>`.
//!
//! If you want to load these certificates into a `rustls::RootCertStore`,
//! you'll likely want to do something like this:
//!
//! ```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(cert).unwrap();
//! }
//! ```
#[cfg(all(unix, not(target_os = "macos")))]
mod unix;
#[cfg(all(unix, not(target_os = "macos")))]
use unix as platform;

#[cfg(windows)]
mod windows;
#[cfg(windows)]
use windows as platform;

#[cfg(target_os = "macos")]
mod macos;
#[cfg(target_os = "macos")]
use macos as platform;

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

use pki_types::CertificateDer;

/// Load root certificates found in the platform's native certificate store.
///
/// This function fails in a platform-specific way, expressed in a `std::io::Error`.
///
/// 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<CertificateDer<'static>>, Error> {
platform::load_native_certs()
}

/// Used inside unix.rs
fn load_pem_certs(path: &Path) -> Result<Vec<CertificateDer<'static>>, Error> {
let f = File::open(path)?;
let mut f = BufReader::new(f);
rustls_pemfile::certs(&mut f)
.collect::<Result<Vec<_>, _>>()
.map_err(|err| {
Error::new(
ErrorKind::InvalidData,
format!("could not load PEM file {path:?}: {err}"),
)
})
}
57 changes: 57 additions & 0 deletions src/rustls-native-certs/src/macos.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
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<CertificateDer<'static>>, Error> {
// The various domains are designed to interact like this:
//
// "Per-user Trust Settings override locally administered
// Trust Settings, which in turn override the System Trust
// Settings."
//
// So we collect the certificates in this order; as a map of
// their DER encoding to what we'll do with them. We don't
// overwrite existing elements, which mean User settings
// trump Admin trump System, as desired.

let mut all_certs = HashMap::new();

for domain in &[Domain::User, Domain::Admin, Domain::System] {
let ts = TrustSettings::new(*domain);
let iter = ts
.iter()
.map_err(|err| Error::new(ErrorKind::Other, err))?;

for cert in iter {
let der = cert.to_der();

// If there are no specific trust settings, the default
// is to trust the certificate as a root cert. Weird API but OK.
// The docs say:
//
// "Note that an empty Trust Settings array means "always trust this cert,
// with a resulting kSecTrustSettingsResult of kSecTrustSettingsResultTrustRoot".
let trusted = ts
.tls_trust_settings_for_certificate(&cert)
.map_err(|err| Error::new(ErrorKind::Other, err))?
.unwrap_or(TrustSettingsForCertificate::TrustRoot);

all_certs.entry(der).or_insert(trusted);
}
}

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() {
use TrustSettingsForCertificate::*;
if let TrustRoot | TrustAsRoot = trusted {
certs.push(CertificateDer::from(der));
}
}

Ok(certs)
}
14 changes: 14 additions & 0 deletions src/rustls-native-certs/src/unix.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
use crate::load_pem_certs;

use pki_types::CertificateDer;

use std::io::Error;

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

match likely_locations.cert_file {
Some(cert_file) => load_pem_certs(&cert_file),
None => Ok(Vec::new()),
}
}
30 changes: 30 additions & 0 deletions src/rustls-native-certs/src/windows.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
use pki_types::CertificateDer;

use std::io::Error;

pub fn load_native_certs() -> Result<Vec<CertificateDer<'static>>, Error> {
// My "Personal"
// Root "Trusted Root Certification Authorities"
// Trust "Enterprise Trust"
// CA "Intermediate Certification Authorities"
let store_names = ["My", "Root", "Trust", "CA"];
let mut certs = Vec::new();

for &store_name in &store_names {
// Try to open the store from the current user context and accumulate certificates
if let Ok(current_user_store) = schannel::cert_store::CertStore::open_current_user(store_name) {
for cert in current_user_store.certs() {
certs.push(CertificateDer::from(cert.to_der().to_vec()));
}
}

// Try to open the store from the local machine context and accumulate certificates
if let Ok(local_machine_store) = schannel::cert_store::CertStore::open_local_machine(store_name) {
for cert in local_machine_store.certs() {
certs.push(CertificateDer::from(cert.to_der().to_vec()));
}
}
}

Ok(certs)
}
4 changes: 2 additions & 2 deletions test.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@ const nativeCerts = require(".")
const ca = nativeCerts.load_native_certs()
assert(ca.length > 0)

https.get({ca, host: "google.com", path: "/"}) // should not fail
// console.log(ca) // uncomment to print the certs for debugging
// https.get({ca, host: "google.com", path: "/"}) // should not fail
console.log(ca) // uncomment to print the certs for debugging

0 comments on commit a213c10

Please sign in to comment.