Skip to content
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
1 change: 0 additions & 1 deletion Lib/test/test_ssl.py
Original file line number Diff line number Diff line change
Expand Up @@ -762,7 +762,6 @@ def test_dealloc_warn(self):
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)
Expand Down
52 changes: 52 additions & 0 deletions crates/host_env/src/native_certs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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);
}
}
29 changes: 5 additions & 24 deletions crates/stdlib/src/ssl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4867,34 +4867,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()
Expand Down
Loading