Skip to content
Merged
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
169 changes: 116 additions & 53 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use std::env;
use std::ffi::{OsStr, OsString};
use std::path::{Path, PathBuf};

/// Probe for SSL certificates on the system, then configure the SSL certificate `SSL_CERT_FILE`
Expand Down Expand Up @@ -30,27 +31,36 @@ pub unsafe fn try_init_openssl_env_vars() -> bool {
// returned them unchanged
if let Some(path) = &cert_file {
unsafe {
put(ENV_CERT_FILE, path);
put(ENV_CERT_FILE, path.as_os_str());
}
}
if let Some(path) = &cert_dir {

if !cert_dir.is_empty() {
let mut joined = OsString::new();
for (i, path) in cert_dir.iter().enumerate() {
if i != 0 {
joined.push(":");
}
joined.push(path.as_os_str());
}

unsafe {
put(ENV_CERT_DIR, path);
put(ENV_CERT_DIR, &joined);
}
}

unsafe fn put(var: &str, path: &Path) {
unsafe fn put(var: &str, path: &OsStr) {
// Avoid calling `setenv` if the variable already has the same contents. This avoids a
// crash when called from out of perl <5.38 (Debian Bookworm is at 5.36), as old versions
// of perl tend to manipulate the `environ` pointer directly.
if env::var_os(var).as_deref() != Some(path.as_os_str()) {
if env::var_os(var).as_deref() != Some(path) {
unsafe {
env::set_var(var, path);
}
}
}

cert_file.is_some() || cert_dir.is_some()
cert_file.is_some() || !cert_dir.is_empty()
}

/// Probe the current system for the "cert file" and "cert dir" variables that
Expand All @@ -59,23 +69,23 @@ pub unsafe fn try_init_openssl_env_vars() -> bool {
/// The probe result is returned as a [`ProbeResult`] structure here.
pub fn probe() -> ProbeResult {
let mut result = ProbeResult::from_env();
for certs_dir in candidate_cert_dirs() {
if result.cert_file.is_none() {
result.cert_file = CERTIFICATE_FILE_NAMES
if result.cert_file.is_none() {
result.cert_file =
CERTIFICATE_FILE_NAMES
.iter()
.map(|fname| certs_dir.join(fname))
.find(|p| p.exists());
}
if result.cert_dir.is_none() {
let cert_dir = certs_dir.join("certs");
if cert_dir.exists() {
result.cert_dir = Some(cert_dir);
}
}
if result.cert_file.is_some() && result.cert_dir.is_some() {
break;
.find_map(|p| match Path::new(p).exists() {
true => Some(PathBuf::from(p)),
false => None,
});
}

for certs_dir in candidate_cert_dirs() {
let cert_dir = PathBuf::from(certs_dir);
if cert_dir.exists() {
result.cert_dir.push(cert_dir);
}
}

result
}

Expand All @@ -98,61 +108,114 @@ pub fn candidate_cert_dirs() -> impl Iterator<Item = &'static Path> {
/// Returns `true` if either variable is set to an existing file or directory.
pub fn has_ssl_cert_env_vars() -> bool {
let probe = ProbeResult::from_env();
probe.cert_file.is_some() || probe.cert_dir.is_some()
probe.cert_file.is_some() || !probe.cert_dir.is_empty()
}

pub struct ProbeResult {
pub cert_file: Option<PathBuf>,
pub cert_dir: Option<PathBuf>,
pub cert_dir: Vec<PathBuf>,
}

impl ProbeResult {
fn from_env() -> ProbeResult {
let var = |name| env::var_os(name).map(PathBuf::from).filter(|p| p.exists());
ProbeResult {
cert_file: var(ENV_CERT_FILE),
cert_dir: var(ENV_CERT_DIR),
cert_dir: match var(ENV_CERT_DIR) {
Some(p) => vec![p],
None => vec![],
},
}
}
}

// see http://gagravarr.org/writing/openssl-certs/others.shtml
// Go's related definitions can be found here:
// https://github.com/golang/go/tree/master/src/crypto/x509
// Look at `root_*.go` files for platform-specific files and directories.

#[cfg(target_os = "linux")]
const CERTIFICATE_DIRS: &[&str] = &[
"/etc/ssl/certs", // SLES 10, SLES 11
"/etc/pki/tls/certs", // Fedora, RHEL
];

#[cfg(target_os = "freebsd")]
const CERTIFICATE_DIRS: &[&str] = &[
"/var/ssl",
"/usr/share/ssl",
"/usr/local/ssl",
"/usr/local/openssl",
"/usr/local/etc/openssl",
"/usr/local/share",
"/usr/lib/ssl",
"/usr/ssl",
"/etc/openssl",
"/etc/pki/ca-trust/extracted/pem",
"/etc/pki/tls",
"/etc/ssl",
"/etc/certs",
"/opt/etc/ssl", // Entware
#[cfg(target_os = "android")]
"/data/data/com.termux/files/usr/etc/tls",
#[cfg(target_os = "haiku")]
"/boot/system/data/ssl",
"/etc/ssl/certs", // FreeBSD 12.2+,
"/usr/local/share/certs", // FreeBSD
];

// cert.pem looks to be an openssl 1.0.1 thing, while
// certs/ca-certificates.crt appears to be a 0.9.8 thing
#[cfg(any(target_os = "illumos", target_os = "solaris"))]
const CERTIFICATE_DIRS: &[&str] = &["/etc/certs/CA"];

#[cfg(target_os = "netbsd")]
const CERTIFICATE_DIRS: &[&str] = &["/etc/openssl/certs"];

#[cfg(target_os = "aix")]
const CERTIFICATE_DIRS: &[&str] = &["/var/ssl/certs"];

#[cfg(not(any(
target_os = "linux",
target_os = "freebsd",
target_os = "illumos",
target_os = "solaris",
target_os = "netbsd",
target_os = "aix"
)))]
const CERTIFICATE_DIRS: &[&str] = &["/etc/ssl/certs"];
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we have an empty CERTIFICATE_DIRS for darwin and windows? (Same for CERTIFICATE_FILE_NAMES.)


#[cfg(target_os = "linux")]
const CERTIFICATE_FILE_NAMES: &[&str] = &[
"cert.pem",
"certs.pem",
"ca-bundle.pem",
"cacert.pem",
"ca-certificates.crt",
"certs/ca-certificates.crt",
"certs/ca-root-nss.crt",
"certs/ca-bundle.crt",
"CARootCertificates.pem",
"tls-ca-bundle.pem",
"/etc/ssl/certs/ca-certificates.crt", // Debian, Ubuntu, Gentoo, Joyent SmartOS, etc.
"/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem", // CentOS, RHEL 7 (should come before RHEL 6)
"/etc/pki/tls/certs/ca-bundle.crt", // Fedora, RHEL 6
"/etc/ssl/ca-bundle.pem", // OpenSUSE
"/etc/pki/tls/cacert.pem", // OpenELEC (a media center Linux distro)
"/etc/ssl/cert.pem", // Alpine Linux
"/opt/etc/ssl/certs/ca-certificates.crt", // Entware, https://github.com/rustls/openssl-probe/pull/21
];

#[cfg(target_os = "freebsd")]
const CERTIFICATE_FILE_NAMES: &[&str] = &["/usr/local/etc/ssl/cert.pem"];

#[cfg(target_os = "dragonfly")]
const CERTIFICATE_FILE_NAMES: &[&str] = &["/usr/local/share/certs/ca-root-nss.crt"];

#[cfg(target_os = "netbsd")]
const CERTIFICATE_FILE_NAMES: &[&str] = &["/etc/openssl/certs/ca-certificates.crt"];

#[cfg(target_os = "openbsd")]
const CERTIFICATE_FILE_NAMES: &[&str] = &["/etc/ssl/cert.pem"];

#[cfg(target_os = "solaris")] // Solaris 11.2+
const CERTIFICATE_FILE_NAMES: &[&str] = &["/etc/certs/ca-certificates.crt"];

#[cfg(target_os = "illumos")]
const CERTIFICATE_FILE_NAMES: &[&str] = &[
"/etc/ssl/cacert.pem", // OmniOS, https://github.com/rustls/openssl-probe/pull/22
"/etc/certs/ca-certificates.crt", // OpenIndiana, https://github.com/rustls/openssl-probe/pull/22
];

#[cfg(target_os = "android")] // Termux on Android, https://github.com/rustls/openssl-probe/pull/2
const CERTIFICATE_FILE_NAMES: &[&str] = &["/data/data/com.termux/files/usr/etc/tls/cert.pem"];

#[cfg(target_os = "haiku")] // https://github.com/rustls/openssl-probe/pull/4
const CERTIFICATE_FILE_NAMES: &[&str] = &["/boot/system/data/ssl/CARootCertificates.pem"];

#[cfg(not(any(
target_os = "linux",
target_os = "freebsd",
target_os = "dragonfly",
target_os = "netbsd",
target_os = "openbsd",
target_os = "solaris",
target_os = "illumos",
target_os = "android",
target_os = "haiku",
)))]
const CERTIFICATE_FILE_NAMES: &[&str] = &["/etc/ssl/certs/ca-certificates.crt"];

/// The OpenSSL environment variable to configure what certificate file to use.
pub const ENV_CERT_FILE: &'static str = "SSL_CERT_FILE";

Expand Down