diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py index e1759f1aa6b..cd624696c02 100644 --- a/Lib/test/test_ssl.py +++ b/Lib/test/test_ssl.py @@ -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) diff --git a/crates/host_env/src/native_certs.rs b/crates/host_env/src/native_certs.rs index a2a35c15965..ace37e6da18 100644 --- a/crates/host_env/src/native_certs.rs +++ b/crates/host_env/src/native_certs.rs @@ -5,6 +5,43 @@ pub struct LoadResult { pub errors: Vec, } +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); + } +} diff --git a/crates/stdlib/src/ssl.rs b/crates/stdlib/src/ssl.rs index 63af8b0ff1b..05610b85971 100644 --- a/crates/stdlib/src/ssl.rs +++ b/crates/stdlib/src/ssl.rs @@ -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()