From 9438c842f4646c8fcbb6e8c49ae3adb18445e389 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Thu, 27 Nov 2025 13:32:34 +0100 Subject: [PATCH 1/4] Use full paths for CERTIFICATE_FILE_NAMES --- src/lib.rs | 57 ++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 40 insertions(+), 17 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 27c6895..f9f045d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -59,13 +59,17 @@ 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()); - } + .find_map(|p| match Path::new(p).exists() { + true => Some(PathBuf::from(p)), + false => None, + }); + } + + for certs_dir in candidate_cert_dirs() { if result.cert_dir.is_none() { let cert_dir = certs_dir.join("certs"); if cert_dir.exists() { @@ -122,7 +126,7 @@ const CERTIFICATE_DIRS: &[&str] = &[ "/usr/share/ssl", "/usr/local/ssl", "/usr/local/openssl", - "/usr/local/etc/openssl", + "/usr/local/etc/openssl", // MacPorts, https://github.com/rustls/openssl-probe/pull/15 "/usr/local/share", "/usr/lib/ssl", "/usr/ssl", @@ -141,16 +145,35 @@ const CERTIFICATE_DIRS: &[&str] = &[ // cert.pem looks to be an openssl 1.0.1 thing, while // certs/ca-certificates.crt appears to be a 0.9.8 thing 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. + #[cfg(target_os = "linux")] + "/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem", // CentOS, RHEL 7 (should come before RHEL 6) + #[cfg(target_os = "linux")] + "/etc/pki/tls/certs/ca-bundle.crt", // Fedora, RHEL 6 + #[cfg(target_os = "linux")] + "/etc/ssl/ca-bundle.pem", // OpenSUSE + #[cfg(target_os = "linux")] + "/etc/pki/tls/cacert.pem", // OpenELEC (a media center Linux distro) + #[cfg(any(target_os = "linux", target_os = "openbsd"))] + "/etc/ssl/cert.pem", // Alpine Linux, OpenBSD + #[cfg(target_os = "freebsd")] + "/usr/local/etc/ssl/cert.pem", // FreeBSD + #[cfg(target_os = "dragonfly")] + "/usr/local/share/certs/ca-root-nss.crt", // DragonFly + #[cfg(target_os = "netbsd")] + "/etc/openssl/certs/ca-certificates.crt", // NetBSD + #[cfg(target_os = "solaris")] + "/etc/certs/ca-certificates.crt", // Solaris 11.2+ + #[cfg(target_os = "illumos")] + "/etc/ssl/cacert.pem", // OmniOS, https://github.com/rustls/openssl-probe/pull/22 + #[cfg(target_os = "illumos")] + "/etc/certs/ca-certificates.crt", // OpenIndiana, https://github.com/rustls/openssl-probe/pull/22 + #[cfg(target_os = "android")] + "/data/data/com.termux/files/usr/etc/tls/cert.pem", // Termux on Android, https://github.com/rustls/openssl-probe/pull/2 + #[cfg(target_os = "haiku")] + "/boot/system/data/ssl/CARootCertificates.pem", // Haiku, https://github.com/rustls/openssl-probe/pull/4 + #[cfg(target_os = "linux")] + "/opt/etc/ssl/certs/ca-certificates.crt", // Entware, https://github.com/rustls/openssl-probe/pull/21 ]; /// The OpenSSL environment variable to configure what certificate file to use. From cd7f18a1b28186f1e4ca3bb17f5d28e67b76e8b8 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Fri, 28 Nov 2025 10:17:02 +0100 Subject: [PATCH 2/4] Use per-platform definitions of CERTIFICATE_FILE_NAMES --- src/lib.rs | 71 +++++++++++++++++++++++++++++++++--------------------- 1 file changed, 44 insertions(+), 27 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index f9f045d..3ae80c0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -142,40 +142,57 @@ const CERTIFICATE_DIRS: &[&str] = &[ "/boot/system/data/ssl", ]; -// 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(target_os = "linux")] const CERTIFICATE_FILE_NAMES: &[&str] = &[ "/etc/ssl/certs/ca-certificates.crt", // Debian, Ubuntu, Gentoo, Joyent SmartOS, etc. - #[cfg(target_os = "linux")] "/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem", // CentOS, RHEL 7 (should come before RHEL 6) - #[cfg(target_os = "linux")] - "/etc/pki/tls/certs/ca-bundle.crt", // Fedora, RHEL 6 - #[cfg(target_os = "linux")] - "/etc/ssl/ca-bundle.pem", // OpenSUSE - #[cfg(target_os = "linux")] - "/etc/pki/tls/cacert.pem", // OpenELEC (a media center Linux distro) - #[cfg(any(target_os = "linux", target_os = "openbsd"))] - "/etc/ssl/cert.pem", // Alpine Linux, OpenBSD - #[cfg(target_os = "freebsd")] - "/usr/local/etc/ssl/cert.pem", // FreeBSD - #[cfg(target_os = "dragonfly")] - "/usr/local/share/certs/ca-root-nss.crt", // DragonFly - #[cfg(target_os = "netbsd")] - "/etc/openssl/certs/ca-certificates.crt", // NetBSD - #[cfg(target_os = "solaris")] - "/etc/certs/ca-certificates.crt", // Solaris 11.2+ - #[cfg(target_os = "illumos")] + "/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 - #[cfg(target_os = "illumos")] "/etc/certs/ca-certificates.crt", // OpenIndiana, https://github.com/rustls/openssl-probe/pull/22 - #[cfg(target_os = "android")] - "/data/data/com.termux/files/usr/etc/tls/cert.pem", // Termux on Android, https://github.com/rustls/openssl-probe/pull/2 - #[cfg(target_os = "haiku")] - "/boot/system/data/ssl/CARootCertificates.pem", // Haiku, https://github.com/rustls/openssl-probe/pull/4 - #[cfg(target_os = "linux")] - "/opt/etc/ssl/certs/ca-certificates.crt", // Entware, https://github.com/rustls/openssl-probe/pull/21 ]; +#[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"; From 86ffc6fe1f372309f00262c88c019f9b427e8b37 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Thu, 27 Nov 2025 13:45:29 +0100 Subject: [PATCH 3/4] Use platform-specific directories --- src/lib.rs | 55 ++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 35 insertions(+), 20 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 3ae80c0..ecd3e8d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -71,15 +71,16 @@ pub fn probe() -> ProbeResult { for certs_dir in candidate_cert_dirs() { if result.cert_dir.is_none() { - let cert_dir = certs_dir.join("certs"); + let cert_dir = PathBuf::from(certs_dir); if cert_dir.exists() { result.cert_dir = Some(cert_dir); } } - if result.cert_file.is_some() && result.cert_dir.is_some() { + if result.cert_dir.is_some() { break; } } + result } @@ -121,27 +122,41 @@ impl ProbeResult { } // 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", // MacPorts, https://github.com/rustls/openssl-probe/pull/15 - "/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 ]; +#[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"]; + #[cfg(target_os = "linux")] const CERTIFICATE_FILE_NAMES: &[&str] = &[ "/etc/ssl/certs/ca-certificates.crt", // Debian, Ubuntu, Gentoo, Joyent SmartOS, etc. From 6be675d3fcd6d3333f9c5b85a122f321f2ed496a Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Thu, 27 Nov 2025 13:50:56 +0100 Subject: [PATCH 4/4] Yield multiple certificate directories --- src/lib.rs | 42 +++++++++++++++++++++++++----------------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index ecd3e8d..138a6c9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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` @@ -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 @@ -70,14 +80,9 @@ pub fn probe() -> ProbeResult { } for certs_dir in candidate_cert_dirs() { - if result.cert_dir.is_none() { - let cert_dir = PathBuf::from(certs_dir); - if cert_dir.exists() { - result.cert_dir = Some(cert_dir); - } - } - if result.cert_dir.is_some() { - break; + let cert_dir = PathBuf::from(certs_dir); + if cert_dir.exists() { + result.cert_dir.push(cert_dir); } } @@ -103,12 +108,12 @@ pub fn candidate_cert_dirs() -> impl Iterator { /// 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, - pub cert_dir: Option, + pub cert_dir: Vec, } impl ProbeResult { @@ -116,7 +121,10 @@ impl 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![], + }, } } }