ssl: discover default certificate paths in host_env (#8649)

Keep host certificate path probing with the native certificate provider, select the first available OpenSSL-style file and directory, and leave environment overrides to ssl.py. This also gives Android string defaults instead of the previous None values.

Co-authored-by: Ivan Mironov <mironov.ivan@gmail.com>

Assisted-by: OpenAI Codex: GPT-5
This commit is contained in:
Jeong, YunWonandIvan Mironov authored and GitHub committed 2026-09-05 02:52:03 +09:00
1 parent d67526cb08
commit 4a267b2616
3 files changed
+57 -25

No files matched your search

-1
View File
@@ -762,7 +762,6 @@ class BasicSocketTests(unittest.TestCase):
support.gc_collect()
self.assertIn(r, str(cm.warning.args[0]))
@unittest.expectedFailureIf(sys.platform == "android", "TODO: RUSTPYTHON; TypeError: path should be string, bytes, os.PathLike or integer, not NoneType")
def test_get_default_verify_paths(self):
paths = ssl.get_default_verify_paths()
self.assertEqual(len(paths), 6)
+52
View File
@@ -5,6 +5,43 @@ pub struct LoadResult {
pub errors: Vec<String>,
}
const DEFAULT_CERT_FILES: &[&str] = &[
"/etc/ssl/certs/ca-certificates.crt",
"/etc/pki/tls/certs/ca-bundle.crt",
"/etc/ssl/ca-bundle.pem",
"/etc/pki/tls/cacert.pem",
"/etc/ssl/cert.pem",
"/usr/local/share/certs/ca-root-nss.crt",
"/usr/local/etc/openssl/cert.pem",
];
const DEFAULT_CERT_DIRS: &[&str] = &[
"/etc/ssl/certs",
"/etc/pki/tls/certs",
"/system/etc/security/cacerts",
"/usr/local/share/certs",
];
fn first_existing<'a>(candidates: &'a [&'a str], exists: impl Fn(&str) -> bool) -> Option<&'a str> {
candidates.iter().copied().find(|path| exists(path))
}
/// Return the host's first available OpenSSL-style certificate file and directory.
/// Environment overrides are intentionally left to Python's `ssl` module.
pub fn default_verify_paths() -> (String, String) {
if cfg!(windows) {
return (String::new(), String::new());
}
let cafile = first_existing(DEFAULT_CERT_FILES, |path| crate::fs::is_file(path))
.unwrap_or("/etc/ssl/cert.pem")
.to_owned();
let capath = first_existing(DEFAULT_CERT_DIRS, |path| crate::fs::is_dir(path))
.unwrap_or("/etc/ssl/certs")
.to_owned();
(cafile, capath)
}
pub fn load() -> LoadResult {
let result = rustls_native_certs::load_native_certs();
LoadResult {
@@ -20,3 +57,18 @@ pub fn load() -> LoadResult {
.collect(),
}
}
#[cfg(test)]
mod tests {
use super::first_existing;
#[test]
fn first_existing_path_preserves_candidate_priority() {
let candidates = ["missing", "first", "second"];
assert_eq!(
first_existing(&candidates, |path| path == "first" || path == "second"),
Some("first")
);
assert_eq!(first_existing(&candidates, |_| false), None);
}
}
+5 -24
View File
@@ -4914,34 +4914,15 @@ mod _ssl {
#[pyfunction]
fn get_default_verify_paths(vm: &VirtualMachine) -> PyObjectRef {
// Return default certificate paths as a tuple
// Lib/ssl.py expects: (openssl_cafile_env, openssl_cafile, openssl_capath_env, openssl_capath)
// parts[0] = environment variable name for cafile
// parts[1] = default cafile path
// parts[2] = environment variable name for capath
// parts[3] = default capath path
// Common default paths for different platforms
// These match the first candidates that rustls-native-certs/openssl-probe checks
let (default_cafile, default_capath): (Option<&str>, Option<&str>) = cfg_select! {
// macOS primarily uses Keychain API, but provides fallback paths
// for compatibility and when Keychain access fails
target_os = "macos" => (Some("/etc/ssl/cert.pem"), Some("/etc/ssl/certs")),
// Linux: matches openssl-probe's first candidate (/etc/ssl/cert.pem)
// openssl-probe checks multiple locations at runtime, but we return
// OpenSSL's compile-time default
target_os = "linux" => (Some("/etc/ssl/cert.pem"), Some("/etc/ssl/certs")),
// Windows uses certificate store, not file paths
// Return empty strings to avoid None being passed to os.path.isfile()
windows => (Some(""), Some("")),
_ => (None, None),
};
let (default_cafile, default_capath) =
rustpython_host_env::native_certs::default_verify_paths();
let tuple = vm.ctx.new_tuple(vec![
vm.ctx.new_str("SSL_CERT_FILE").into(), // openssl_cafile_env
default_cafile.map_or_else(|| vm.ctx.none(), |s| vm.ctx.new_str(s).into()), // openssl_cafile
vm.ctx.new_str("SSL_CERT_DIR").into(), // openssl_capath_env
default_capath.map_or_else(|| vm.ctx.none(), |s| vm.ctx.new_str(s).into()), // openssl_capath
vm.ctx.new_str(default_cafile).into(), // openssl_cafile
vm.ctx.new_str("SSL_CERT_DIR").into(), // openssl_capath_env
vm.ctx.new_str(default_capath).into(), // openssl_capath
]);
tuple.into()