-
Notifications
You must be signed in to change notification settings - Fork 1.4k
ssl module for windows #6332
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
ssl module for windows #6332
Conversation
WalkthroughRelaxed hostname validation to allow IP addresses (no SNI for IPs); added Windows schannel-based loading of system certificates (ROOT and CA) and Windows-specific defaults; introduced Windows-only cert and CRL enumeration functions; exposed those functions in the public Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~30–45 minutes
Suggested reviewers
Poem
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (2)
crates/stdlib/src/ssl.rs (2)
1532-1541: Inconsistent error handling between certificate loading calls.
load_system_certificateserrors are silently ignored withlet _ = ..., whiletry_load_from_python_environpropagates errors with?. This inconsistency could mask legitimate failures.Consider either:
- Using
?for both to propagate errors consistently- Using
let _ = ...for both if best-effort loading is intended- Adding a comment explaining why one can fail silently but not the other
- // Load system certificates first (before creating loader to avoid borrow conflicts) - let _ = self.load_system_certificates(&mut store, vm); + // Load system certificates first (before creating loader to avoid borrow conflicts) + // Silently ignore errors - fallback to webpki-roots below if no certs loaded + let _ = self.load_system_certificates(&mut store, vm);
4504-4556:enum_crlsuses unsafe Windows API - review carefully.The implementation correctly:
- Opens the store and checks for null
- Iterates CRLs with proper null termination check
- Closes the store at the end
However, there's a potential issue: if
store_name.as_str().encode_utf16()produces an invalid store name that opens successfully but causesCertEnumCRLsInStoreto behave unexpectedly, the loop would still safely terminate on null.One concern: the
CertCloseStoreat line 4553 is always called, but if early returns were added in the future (e.g., for error handling in the loop), the store could leak. Consider using a RAII wrapper ordefer!pattern.For improved safety, consider wrapping the store handle in a struct with
Drop:struct CertStoreHandle(windows_sys::Win32::Security::Cryptography::HCERTSTORE); impl Drop for CertStoreHandle { fn drop(&mut self) { if !self.0.is_null() { unsafe { CertCloseStore(self.0, 0) }; } } }
📜 Review details
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
crates/stdlib/src/ssl.rs(7 hunks)crates/vm/src/stdlib/builtins.rs(1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.rs
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
**/*.rs: Follow the default rustfmt code style by runningcargo fmtto format Rust code
Always run clippy to lint Rust code (cargo clippy) before completing tasks and fix any warnings or lints introduced by changes
Follow Rust best practices for error handling and memory management
Use the macro system (pyclass,pymodule,pyfunction, etc.) when implementing Python functionality in Rust
Files:
crates/stdlib/src/ssl.rscrates/vm/src/stdlib/builtins.rs
🧬 Code graph analysis (2)
crates/stdlib/src/ssl.rs (2)
crates/stdlib/src/ssl/cert.rs (1)
is_ca_certificate(270-286)crates/stdlib/src/ssl/compat.rs (1)
writer(318-323)
crates/vm/src/stdlib/builtins.rs (1)
crates/vm/src/stdlib/os.rs (1)
extend_module(1573-1597)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
- GitHub Check: Run tests under miri
- GitHub Check: Check the WASM package and demo
- GitHub Check: Run snippets and cpython tests (windows-latest)
- GitHub Check: Run snippets and cpython tests (macos-latest)
- GitHub Check: Check Rust code with clippy
- GitHub Check: Run snippets and cpython tests (ubuntu-latest)
- GitHub Check: Run rust tests (macos-latest)
- GitHub Check: Run rust tests (windows-latest)
- GitHub Check: Run snippets and cpython tests on wasm-wasi
- GitHub Check: Run rust tests (ubuntu-latest)
- GitHub Check: Ensure compilation on various targets
🔇 Additional comments (7)
crates/vm/src/stdlib/builtins.rs (1)
1190-1195: LGTM!The
WindowsErroralias forOSErrorfollows the same pattern as existing aliases (IOError,EnvironmentErrorat lines 1138-1139) and correctly uses#[cfg(windows)]for platform-specific compilation. This matches CPython's behavior whereWindowsErroris available as anOSErroralias on Windows.crates/stdlib/src/ssl.rs (6)
481-483: LGTM!Allowing IP addresses as
server_hostnamewhile disabling SNI for them is the correct behavior per RFC 6066. SNI should only contain DNS hostnames, not IP addresses. This aligns with CPython 3.7+ behavior.
1931-1932: LGTM!The comment correctly documents that IP addresses are permitted as
server_hostname, consistent with thevalidate_hostnamefunction changes.
3333-3341: LGTM!Returning empty strings for Windows is appropriate since Windows uses the system certificate store rather than file paths. The comment correctly explains this prevents
Nonebeing passed toos.path.isfile()in Python'sssl.py.
3430-3484: Chunked write implementation looks correct but consider edge case.The 16KB chunking aligns with typical TLS record sizes and prevents buffer overflow issues. However, if a timeout or error occurs mid-write (e.g., at line 3468-3469), the function returns an error without indicating how many bytes were successfully sent. This differs from typical socket semantics where partial writes return the count.
This is acceptable for SSL semantics (all-or-nothing writes are common), but worth documenting if this behavior diverges from CPython's
SSLSocket.write().
4453-4502:enum_certificatesimplementation looks solid.Good use of the
schannelcrate's safe abstractions. The conversion ofValidUsesto either a boolean or frozenset correctly matches CPython's expected return format.
1453-1522: The Windows certificate loading implementation follows schannel best practices by opening both ROOT and CA stores from both current user and local machine contexts, properly converting certificates to DER format, and tracking CA vs. non-CA certificates. Usingrustls-native-certsfor non-Windows platforms is appropriate. Verification of the schannel dependency in Cargo.toml and confirmation that the code passes cargo clippy checks could not be completed due to repository access limitations.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🧹 Nitpick comments (4)
crates/stdlib/src/ssl.rs (4)
1532-1554: Document platform-specific certificate loading order.The implementation has different certificate loading strategies for Windows vs. non-Windows:
- Windows: System certs first, then environment variables (additively)
- Non-Windows: Environment variables first, system certs only as fallback
This could be confusing for users expecting consistent behavior. Additionally, the Windows path silently ignores errors from
load_system_certificates(line 1536), while the non-Windows path checks success (line 1549).Consider adding a module-level doc comment explaining this platform difference, or align the behavior if there's no strong reason for the difference. If the difference is intentional (e.g., to match CPython's test expectations), document it clearly:
// Platform-specific certificate loading strategies: // - Windows: Always loads system store + env vars (matches test_load_default_certs_env_windows) // - Unix: Prefers env vars over system (matches test_load_default_certs_env)
3443-3497: Consider implications of chunked write implementation.The write method now processes data in 16KB chunks to avoid filling rustls's internal buffer. However, there are potential issues:
Partial write semantics: If a chunk fails to flush to the socket (line 3469-3495), the method returns an error, but some chunks may have already been written to the TLS layer. This differs from traditional socket write semantics where the return value indicates how much was written.
Performance impact: The method now makes multiple flush attempts (one per chunk) rather than buffering everything. For small writes, this could increase overhead.
Non-blocking behavior: In non-blocking mode, if a chunk fails with
SSLWantWriteError, previously written chunks are lost from the caller's perspective.Consider either:
- Tracking
writtenbytes and returning partial success instead of error- Documenting that this matches SSL_write_ex behavior (all-or-nothing in SSL layer)
- Adding a comment explaining why chunking is necessary for rustls
// Write data in chunks to avoid filling rustls's internal buffer. // rustls has a limited internal buffer (~16KB), so we need to flush periodically. // Note: This follows SSL_write_ex semantics - either all data is accepted or an error is returned.
4488-4514: Consider error handling consistency for certificate usage.The error handling for
c.valid_uses()(lines 4499-4511) catches all errors and falls back to returningtrue, which could mask genuine errors. This differs from the similar code in theopenssl.rscontext snippet (lines 3661-3666 in relevant_code_snippets), which propagates errors:// openssl.rs pattern: let usage: PyObjectRef = match c.valid_uses().map_err(|e| e.to_pyexception(vm))? { ValidUses::All => vm.ctx.new_bool(true).into(), // ... };vs. current implementation:
// Current pattern: let usage: PyObjectRef = match c.valid_uses() { Ok(ValidUses::All) => vm.ctx.new_bool(true).into(), Ok(ValidUses::Oids(oids)) => { /* ... */ }, Err(_) => vm.ctx.new_bool(true).into(), // Masks error };Consider propagating errors for better diagnostics, or document why errors should be silently ignored. If this matches CPython's behavior of treating invalid usage as "all uses", add a comment explaining the rationale.
4517-4569: Improve safety and error handling in enum_crls.The function uses extensive unsafe code with raw Windows APIs. While the implementation appears correct, there are safety and maintenance concerns:
- Unsafe pointer operations (lines 4550-4552): Dereferencing raw pointers and creating slices without validation
- Resource cleanup (line 4566): Store cleanup happens at the end, but if a panic occurs during iteration, the store won't be closed
- Inconsistency: Uses
windows_sysFFI directly, whileenum_certificatesuses the higher-levelschannelcrateConsider these improvements:
- Use RAII for store cleanup:
// Add a guard struct struct StoreGuard(windows_sys::Win32::Security::Cryptography::HCERTSTORE); impl Drop for StoreGuard { fn drop(&mut self) { unsafe { CertCloseStore(self.0, 0) }; } } let _store_guard = StoreGuard(store);
- Add safety comments documenting the invariants for each unsafe block:
// SAFETY: crl_context is valid because CertEnumCRLsInStore returned non-null let crl = unsafe { &*crl_context };
- Consider using schannel for consistency with
enum_certificates, if it provides CRL enumeration APIs.
📜 Review details
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
crates/stdlib/src/ssl.rs(7 hunks)crates/vm/src/stdlib/builtins.rs(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- crates/vm/src/stdlib/builtins.rs
🧰 Additional context used
📓 Path-based instructions (1)
**/*.rs
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
**/*.rs: Follow the default rustfmt code style by runningcargo fmtto format Rust code
Always run clippy to lint Rust code (cargo clippy) before completing tasks and fix any warnings or lints introduced by changes
Follow Rust best practices for error handling and memory management
Use the macro system (pyclass,pymodule,pyfunction, etc.) when implementing Python functionality in Rust
Files:
crates/stdlib/src/ssl.rs
🧬 Code graph analysis (1)
crates/stdlib/src/ssl.rs (2)
crates/stdlib/src/openssl.rs (5)
open_fns(3639-3642)new(3689-3699)enum_certificates(3632-3667)certs(1377-1386)certs(3665-3665)crates/stdlib/src/ssl/cert.rs (2)
is_ca_certificate(270-286)new(627-635)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
- GitHub Check: Run tests under miri
- GitHub Check: Check the WASM package and demo
- GitHub Check: Run snippets and cpython tests on wasm-wasi
- GitHub Check: Ensure compilation on various targets
- GitHub Check: Check Rust code with clippy
- GitHub Check: Run snippets and cpython tests (windows-latest)
- GitHub Check: Run snippets and cpython tests (ubuntu-latest)
- GitHub Check: Run snippets and cpython tests (macos-latest)
- GitHub Check: Run rust tests (macos-latest)
- GitHub Check: Run rust tests (ubuntu-latest)
- GitHub Check: Run rust tests (windows-latest)
🔇 Additional comments (1)
crates/stdlib/src/ssl.rs (1)
481-482: Verify SNI suppression for IP addresses matches the implementation.The comments state that SNI will not be sent for IP addresses, but the
validate_hostnamefunction doesn't explicitly distinguish between hostnames and IPs. Confirm thatServerName::try_from()(or the rustls library it uses) automatically handles this distinction and suppresses SNI for IP addresses as documented. If SNI suppression relies on rustls internals, add implementation details in code or comments to clarify the mechanism.
| #[cfg(windows)] | ||
| let (default_cafile, default_capath) = { | ||
| // Windows uses certificate store, not file paths | ||
| // Return empty strings to avoid None being passed to os.path.isfile() | ||
| (Some(""), Some("")) | ||
| }; | ||
|
|
||
| #[cfg(not(any(target_os = "macos", target_os = "linux", windows)))] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🌐 Web query:
CPython ssl.get_default_verify_paths() Windows openssl_cafile openssl_capath return values
💡 Result:
Short answer:
-
ssl.get_default_verify_paths() returns a DefaultVerifyPaths(namedtuple) with fields cafile, capath, openssl_cafile_env, openssl_cafile, openssl_capath_env, openssl_capath. The cafile/capath fields are the resolved paths or None if the file/dir does not exist. [1]
-
On Windows, the OpenSSL hard‑coded defaults that Python exposes are typically:
- openssl_cafile_env = "SSL_CERT_FILE"
- openssl_cafile = "C:\Program Files\Common Files\SSL\cert.pem"
- openssl_capath_env = "SSL_CERT_DIR"
- openssl_capath = "C:\Program Files\Common Files\SSL\certs"
The runtime cafile and capath (the cafile/capath fields) will be those resolved paths if they exist, otherwise None. [1][2]
Sources:
- Python ssl.get_default_verify_paths() docs. [1]
- Explanation / defaults for OpenSSL on Windows (example used by PyOxidizer and others). [2]
Return None instead of empty strings for Windows certificate paths to match CPython behavior.
CPython's ssl.get_default_verify_paths() returns None for the cafile and capath fields when the hardcoded default paths do not exist on Windows (rather than returning empty strings). The current implementation returning Some("") on Windows (lines 4349-4350) deviates from this behavior. Update to return None or the actual path if it exists, consistent with how macOS and Linux are handled.
🤖 Prompt for AI Agents
In crates/stdlib/src/ssl.rs around lines 4346 to 4353, the Windows branch
currently returns Some("") for (default_cafile, default_capath) which differs
from CPython; change the Windows branch to return (None, None) when no valid
cafile/capath exists (or the actual path wrapped in Some(...) if you detect a
real file/directory), matching the macOS/Linux logic and CPython behavior so
absent/default paths are represented as None rather than empty strings.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
crates/vm/src/exceptions.rs (1)
1495-1511: Consider matching CPython’s errno/winerror precedence inPyOSError::slot_init.On Windows, you always overwrite
args[0]withwinerror_to_errno(winerror_int)when a positional winerror is present. In CPython,OSError.__init__only deriveserrnofromwinerrorwhenerrnois not explicitly provided (or is falsy). This change means callers that pass both anerrnoandwinerrorpositionally will have their explicit errno silently replaced.If you want full CPython compatibility, consider only rewriting
new_args.args[0]when the original errno is missing/0, mirroring CPython’s behavior. Otherwise, a short comment that this intentionally diverges from CPython would help future readers.crates/stdlib/src/ssl.rs (1)
462-493: Updatevalidate_hostnamedocstring to reflect that IP addresses are now allowed.The doc comment lists "Is not an IP address (SNI requires DNS names)" as a validation requirement, but the function now explicitly allows IP addresses (lines 481–482) and performs only basic syntactic checks. Update the docstring to describe the actual behavior: IP literals are accepted here; SNI suppression happens elsewhere.
♻️ Duplicate comments (1)
crates/stdlib/src/ssl.rs (1)
4346-4353: Windowsget_default_verify_paths()still returns""instead ofNone(same concern as earlier review).On Windows you keep returning
Some("")for bothdefault_cafileanddefault_capath, which becomes empty strings in the namedtuple rather thanNone. CPython’sssl.get_default_verify_paths()returnsNonefor those fields when there is no actual default path. If downstream Python code or tests rely onis Nonesemantics rather than falsy-string checks, this remains a behavioral divergence.If you decide to keep the empty-string approach (to avoid
Nonebeing passed intoos.path.isfile, etc.), please document that deviation clearly in a comment here or in the Python-side wrapper.
🧹 Nitpick comments (5)
crates/stdlib/src/ssl.rs (5)
1931-1953: Reusevalidate_hostname()in_wrap_socketto keep semantics consistent.
_wrap_socket()currently reimplements hostname checks inline (empty, leading dot, NUL) and comments that IPs are allowed, but it:
- Doesn’t enforce the 253-character limit enforced by
validate_hostname().- Duplicates logic now shared by
_wrap_bioandset_server_hostname().Consider replacing the inline checks with a call to
validate_hostname(hostname, vm)?;to keep all entry points (wrap_socket, wrap_bio, set_server_hostname) in sync, and so any future hostname rules change in one place.
1450-1521: Windows vs non-Windows system cert loading looks reasonable; consider error reporting consistency.The new
load_system_certificates()splits Windows and non-Windows paths cleanly:
- Windows: enumerates
ROOTandCAvia schannel stores for both CurrentUser and LocalMachine, counting total and CA certs.- Non-Windows: uses
rustls_native_certs::load_native_certs()and similarly updates stats.On Windows, you convert “no certs loaded” into
OSError("Failed to load certificates from Windows store"), but callers (load_default_certs) immediately ignore that error and fall back to env/webpki. On non-Windows, you only raise when there are both 0 certs and non-emptyerrors. If you want consistent diagnostics, you might either:
- Make Windows also return details when available (e.g., last OS error), or
- Treat the “0 certs loaded” case the same way as non-Windows and only raise when there’s an explicit error from the loader.
Not strictly required, but it would make behavior and error reporting more uniform across platforms.
1532-1554: load_default_certs Windows/non-Windows ordering matches intent; consider surfacing failure explicitly in logs/tests.The new split in
load_default_certs():
- Windows: always attempts system store first (via schannel), then optionally adds env-based certs.
- Non-Windows: prefers env-based
SSL_CERT_FILE/SSL_CERT_DIR, falling back to system store only if env doesn’t yield anything.This mirrors CPython’s documented/tested behavior. Since both paths eventually fall back to
webpki_rootswhenx509_cert_count == 0, the user never sees a hard failure. If you want easier debugging of “why are my system CAs ignored?”, consider emitting a warning or debug log when system-store loading fails but you silently proceed to the Mozilla bundle.
3443-3499: Chunked writes + explicit TLS flushing look correct; watch for performance overhead.The new
write()path:
- Writes in 16KB chunks into
conn.writer().- After each chunk, flushes pending TLS data via
write_pending_tls()in BIO mode orwrite_tls+sock_sendin socket mode, honoring timeouts and mapping blocking errors to WANT_WRITE.Semantically this should avoid overfilling rustls’ internal buffers and better match OpenSSL-style behavior. The main downside is per-chunk allocation of a
Vec<u8>in the socket mode flush path; if this proves hot under large writes, consider reusing a single buffer or using a stack-allocated array instead of reallocating each iteration.
4466-4515:enum_certificatesimplementation looks solid; consider reducing unsafe surface.The Windows-only
enum_certificates():
- Opens both CurrentUser and LocalMachine stores for the given name.
- Returns an
OSErrorif neither can be opened.- Emits tuples of
(der_bytes, enc_type, usage)whereenc_typeis"x509_asn","pkcs_7_asn", or the raw encoding integer, andusageis eitherTrueor a frozenset of OID strings based onValidUses.The unsafe block only dereferences the
CERT_CONTEXTpointer to readdwCertEncodingType. If theschannelcrate ever exposes the encoding type safely, it would be preferable to use that API to avoid direct struct casting viawindows_sys::Win32::Security::Cryptography::CERT_CONTEXT. For now this looks correct but slightly brittle across crate-version changes.
📜 Review details
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
crates/stdlib/src/ssl.rs(7 hunks)crates/vm/src/exceptions.rs(2 hunks)crates/vm/src/stdlib/builtins.rs(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- crates/vm/src/stdlib/builtins.rs
🧰 Additional context used
📓 Path-based instructions (1)
**/*.rs
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
**/*.rs: Follow the default rustfmt code style by runningcargo fmtto format Rust code
Always run clippy to lint Rust code (cargo clippy) before completing tasks and fix any warnings or lints introduced by changes
Follow Rust best practices for error handling and memory management
Use the macro system (pyclass,pymodule,pyfunction, etc.) when implementing Python functionality in Rust
Files:
crates/stdlib/src/ssl.rscrates/vm/src/exceptions.rs
🧬 Code graph analysis (2)
crates/stdlib/src/ssl.rs (1)
crates/stdlib/src/ssl/cert.rs (1)
is_ca_certificate(270-286)
crates/vm/src/exceptions.rs (1)
crates/common/src/os.rs (1)
winerror_to_errno(110-208)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
- GitHub Check: Run snippets and cpython tests on wasm-wasi
- GitHub Check: Check the WASM package and demo
- GitHub Check: Run snippets and cpython tests (macos-latest)
- GitHub Check: Run snippets and cpython tests (windows-latest)
- GitHub Check: Run snippets and cpython tests (ubuntu-latest)
- GitHub Check: Check Rust code with clippy
- GitHub Check: Run rust tests (macos-latest)
- GitHub Check: Run rust tests (windows-latest)
- GitHub Check: Ensure compilation on various targets
🔇 Additional comments (2)
crates/vm/src/exceptions.rs (1)
1523-1555: OSError__str__formatting with[WinError X]/[Errno X]looks correct.The new formatting logic correctly prefers
winerrorwhen set on Windows, falls back to errno otherwise, and incorporatesfilename/filename2into messages in the expected CPython-style forms like"[WinError X] msg: 'file' -> 'file2'"and"[Errno X] msg". This should make OSError messages much closer to CPython on all platforms.crates/stdlib/src/ssl.rs (1)
4517-4569: Free CRL contexts when enumerating to avoid leaking native resources.In
enum_crls()you loop overCertEnumCRLsInStore(store, crl_context)but never callCertFreeCRLContext()on the returnedCRL_CONTEXTpointers. Per the Win32 API contract,CertEnumCRLsInStoredoes not automatically free the previously returned context—you must callCertFreeCRLContext()explicitly to avoid accumulating unmanaged memory.The standard pattern is to track the previous context pointer and free it before requesting the next one, then free the final context after the loop exits:
let mut crl_context: *const CRL_CONTEXT = std::ptr::null(); loop { + let prev = crl_context; crl_context = unsafe { CertEnumCRLsInStore(store, crl_context) }; + if !prev.is_null() { + unsafe { CertFreeCRLContext(prev) }; + } if crl_context.is_null() { break; } let crl = unsafe { &*crl_context }; // ... process crl ... } + if !crl_context.is_null() { + unsafe { CertFreeCRLContext(crl_context) }; + }This ensures that every
CRL_CONTEXTpointer obtained during enumeration is properly freed, preventing resource leaks on repeated calls.Likely an incorrect or invalid review comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (1)
crates/stdlib/src/ssl.rs (1)
4346-4351: Inconsistent with CPython: Should return None instead of empty strings.This issue was previously raised in review comments. CPython's
ssl.get_default_verify_paths()returnsNoneforcafileandcapathfields when Windows certificate store is used (rather than empty strings). The current implementation returningSome("")deviates from this behavior.Apply this diff to match CPython behavior:
#[cfg(windows)] let (default_cafile, default_capath) = { // Windows uses certificate store, not file paths - // Return empty strings to avoid None being passed to os.path.isfile() - (Some(""), Some("")) + (None, None) };The Python
ssl.pymodule handlesNonevalues correctly in its logic.
🧹 Nitpick comments (4)
crates/stdlib/src/ssl.rs (4)
1459-1491: Consider logging certificate load failures for diagnostics.The current implementation silently skips certificates when
store.add()fails (line 1476). While this approach is pragmatic for continuing the load process, diagnostic information about why specific certificates were rejected could be valuable for troubleshooting.If rustls provides error details, consider logging them:
let is_ca = cert::is_ca_certificate(cert.as_ref()); - if store.add(cert).is_ok() { + match store.add(cert) { + Ok(_) => { *self.x509_cert_count.write() += 1; if is_ca { *self.ca_cert_count.write() += 1; } + } + Err(e) => { + // Log certificate rejection for diagnostics + eprintln!("Warning: Failed to add certificate from {} store: {:?}", store_name, e); + } }
3443-3497: Verify chunk flushing frequency for performance.The implementation chunks writes into 16KB blocks and flushes after each chunk. While this prevents internal buffer overflow, it may introduce unnecessary system calls for large writes.
Consider flushing less frequently or only when the internal buffer indicates it's needed:
const CHUNK_SIZE: usize = 16384; const FLUSH_THRESHOLD: usize = CHUNK_SIZE * 4; // Flush every 64KB let mut bytes_since_flush = 0; while written < data.len() { let chunk_end = std::cmp::min(written + CHUNK_SIZE, data.len()); let chunk = &data[written..chunk_end]; { let mut writer = conn.writer(); use std::io::Write; writer.write_all(chunk) .map_err(|e| vm.new_os_error(format!("Write failed: {e}")))?; } written = chunk_end; bytes_since_flush += chunk.len(); // Flush when threshold reached or on last chunk if bytes_since_flush >= FLUSH_THRESHOLD || written >= data.len() { if conn.wants_write() { // ... existing flush logic ... } bytes_since_flush = 0; } }This reduces the number of flushes while still preventing buffer overflow. However, verify with benchmarks whether this optimization is beneficial.
4488-4513: Verify safety of unsafe pointer operations.The code performs unsafe pointer dereferences on certificate contexts (line 4490-4493) without explicit null checks. While schannel's API should guarantee valid pointers, documenting this assumption would improve safety.
Add a safety comment or assertion:
let cert = vm.ctx.new_bytes(c.to_der().to_owned()); let enc_type = unsafe { + // Safety: schannel's CertContext::as_ptr() always returns a valid pointer + // to an initialized CERT_CONTEXT structure let ptr = c.as_ptr() as *const Cryptography::CERT_CONTEXT; + debug_assert!(!ptr.is_null(), "Certificate context pointer should never be null"); (*ptr).dwCertEncodingType };Additionally, verify with the schannel crate documentation that
cert_context.as_ptr()guarantees non-null pointers.
4517-4569: Consider RAII wrapper to prevent resource leaks.The function manually manages the certificate store handle with
CertOpenSystemStoreWandCertCloseStore. If a panic occurs between opening and closing (e.g., during memory allocation or Python object creation), the store handle will leak.Implement a RAII guard to ensure cleanup:
struct StoreHandle(windows_sys::Win32::Security::Cryptography::HCERTSTORE); impl Drop for StoreHandle { fn drop(&mut self) { if !self.0.is_null() { unsafe { CertCloseStore(self.0, 0) }; } } } #[cfg(windows)] #[pyfunction] fn enum_crls(store_name: PyStrRef, vm: &VirtualMachine) -> PyResult<Vec<PyObjectRef>> { use windows_sys::Win32::Security::Cryptography::{ CRL_CONTEXT, CertCloseStore, CertEnumCRLsInStore, CertOpenSystemStoreW, X509_ASN_ENCODING, }; let store_name_wide: Vec<u16> = store_name .as_str() .encode_utf16() .chain(std::iter::once(0)) .collect(); let store = unsafe { CertOpenSystemStoreW(0, store_name_wide.as_ptr()) }; if store.is_null() { return Err(vm.new_os_error(format!( "failed to open certificate store {:?}", store_name.as_str() ))); } let _store_guard = StoreHandle(store); // Ensures cleanup on panic or early return // ... rest of implementation ... }This ensures the store is always closed, even if an exception is raised during processing.
📜 Review details
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
Lib/test/test_ssl.pyis excluded by!Lib/**
📒 Files selected for processing (1)
crates/stdlib/src/ssl.rs(7 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.rs
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
**/*.rs: Follow the default rustfmt code style by runningcargo fmtto format Rust code
Always run clippy to lint Rust code (cargo clippy) before completing tasks and fix any warnings or lints introduced by changes
Follow Rust best practices for error handling and memory management
Use the macro system (pyclass,pymodule,pyfunction, etc.) when implementing Python functionality in Rust
Files:
crates/stdlib/src/ssl.rs
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
- GitHub Check: Run snippets and cpython tests on wasm-wasi
- GitHub Check: Run tests under miri
- GitHub Check: Check the WASM package and demo
- GitHub Check: Check Rust code with clippy
- GitHub Check: Run rust tests (windows-latest)
- GitHub Check: Run rust tests (macos-latest)
- GitHub Check: Run rust tests (ubuntu-latest)
- GitHub Check: Run snippets and cpython tests (windows-latest)
- GitHub Check: Run snippets and cpython tests (macos-latest)
- GitHub Check: Run snippets and cpython tests (ubuntu-latest)
- GitHub Check: Ensure compilation on various targets
🔇 Additional comments (2)
crates/stdlib/src/ssl.rs (2)
481-482: LGTM: IP address handling is correct and well-documented.The change to allow IP addresses in server_hostname while documenting that SNI will not be sent aligns with RFC 6066 requirements and CPython behavior. The implementation correctly handles this case elsewhere in the codebase.
1532-1554: LGTM: Platform-specific certificate loading precedence is correct.The implementation correctly handles Windows vs. non-Windows certificate loading order as documented in the comments. Windows loads system certificates first (via schannel), while non-Windows prioritizes environment variables with system certs as fallback.
The silent error handling (
let _ = ...) is consistent with the fallback approach where the final webpki-roots ensures some certificates are always available (line 1558).
| 'Cannot create a client socket with a PROTOCOL_TLS_SERVER context', | ||
| str(e.exception)) | ||
|
|
||
| @unittest.skip("TODO: RUSTPYTHON; Flaky on windows") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no idea why this is failing
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
crates/stdlib/src/ssl.rs (1)
3442-3496: Potential data duplication issue with chunked writes in non-blocking mode.The chunking logic (writing 16KB at a time, flushing after each chunk) can lead to data duplication or corruption in non-blocking mode:
- Chunk 1 writes successfully and flushes to socket (data on wire)
- Chunk 2 writes to TLS buffer but flush fails with
WANT_WRITEerror- Function raises
SSLWantWriteErrorto caller- Caller retries
write()with the same full buffer- Retry writes chunk 1 again to TLS layer (duplicate) while chunk 1 is already sent
This differs from CPython's
SSL_write(), which maintains internal state to handle retries correctly. The issue stems from implementing chunking at the application level rather than within the TLS layer.Consider one of these approaches:
- Remove chunking and write all data at once (simpler, but may use more memory)
- Add internal state to track which chunks were successfully sent
- Expose a flush() method and document that callers must handle partial writes
For now, this primarily affects non-blocking socket mode, which is less common than blocking or BIO mode.
♻️ Duplicate comments (1)
crates/stdlib/src/ssl.rs (1)
4345-4352: Unresolved: Return None instead of empty strings for Windows paths.This was flagged in a previous review but remains unchanged. CPython's
ssl.get_default_verify_paths()returnsNone(not empty strings) when default paths don't exist on Windows.The comment "Return empty strings to avoid None being passed to os.path.isfile()" is not the correct rationale—
os.path.isfile(None)should raise TypeError when no valid path exists, which is the expected behavior.Apply this diff to match CPython behavior:
#[cfg(windows)] let (default_cafile, default_capath) = { // Windows uses certificate store, not file paths - // Return empty strings to avoid None being passed to os.path.isfile() - (Some(""), Some("")) + // Return None to match CPython behavior when no file paths exist + (None, None) };Based on past review comments at lines 4345-4352.
🧹 Nitpick comments (1)
crates/stdlib/src/ssl.rs (1)
4465-4514: Consider logging warnings for silently skipped certificates.The function uses
filter_mapandflat_map(line 4487) which silently skips certificates that fail to process. While this may be intentional (system stores can contain unsupported formats), silently dropping certificates could hide issues during debugging.Consider one of these optional improvements:
- Log a warning when certificates are skipped (if logging is available)
- Add a comment documenting that invalid/unsupported certificates are intentionally skipped
- Collect and return error information for troubleshooting
This is a recommended enhancement for observability, not a functional issue.
📜 Review details
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
Lib/test/test_ssl.pyis excluded by!Lib/**
📒 Files selected for processing (1)
crates/stdlib/src/ssl.rs(7 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.rs
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
**/*.rs: Follow the default rustfmt code style by runningcargo fmtto format Rust code
Always run clippy to lint Rust code (cargo clippy) before completing tasks and fix any warnings or lints introduced by changes
Follow Rust best practices for error handling and memory management
Use the macro system (pyclass,pymodule,pyfunction, etc.) when implementing Python functionality in Rust
Files:
crates/stdlib/src/ssl.rs
🧬 Code graph analysis (1)
crates/stdlib/src/ssl.rs (1)
crates/stdlib/src/ssl/cert.rs (1)
is_ca_certificate(270-286)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (10)
- GitHub Check: Run snippets and cpython tests on wasm-wasi
- GitHub Check: Check Rust code with clippy
- GitHub Check: Run snippets and cpython tests (ubuntu-latest)
- GitHub Check: Run snippets and cpython tests (windows-latest)
- GitHub Check: Check the WASM package and demo
- GitHub Check: Run snippets and cpython tests (macos-latest)
- GitHub Check: Run rust tests (windows-latest)
- GitHub Check: Run rust tests (macos-latest)
- GitHub Check: Run rust tests (ubuntu-latest)
- GitHub Check: Ensure compilation on various targets
🔇 Additional comments (3)
crates/stdlib/src/ssl.rs (3)
481-482: LGTM: Correctly allows IP addresses for server_hostname.The relaxed validation is correct per RFC 6066, which specifies that SNI (Server Name Indication) is only sent for DNS hostnames, not IP addresses. Allowing IP addresses while documenting that SNI won't be sent matches the expected TLS behavior.
1531-1553: LGTM: Platform-specific certificate loading order is intentional.The different loading order (Windows: system first, then env; non-Windows: env first, then system) aligns with platform conventions:
- Windows: System certificate store is the primary trust anchor
- Unix/Linux: Environment variables commonly override system defaults (e.g., in containers)
The test references confirm this is deliberate design.
4516-4568: Unsafe Windows API usage appears correct.The function properly:
- Checks for null store handle before proceeding
- Validates CRL context pointer before dereferencing
- Always closes the store handle (no early returns)
- Uses
slice::from_raw_partswith validated lengthThe unsafe blocks handle the Windows CRL enumeration API correctly. Standard unsafe code review practices apply: ensure the Windows API contracts are upheld (pointer validity, lifetime, etc.).
| #[cfg(windows)] | ||
| { | ||
| // Windows: Use schannel to load from both ROOT and CA stores | ||
| use schannel::cert_store::CertStore; | ||
|
|
||
| let store_names = ["ROOT", "CA"]; | ||
| let open_fns = [CertStore::open_current_user, CertStore::open_local_machine]; | ||
|
|
||
| for store_name in store_names { | ||
| for open_fn in &open_fns { | ||
| if let Ok(cert_store) = open_fn(store_name) { | ||
| for cert_ctx in cert_store.certs() { | ||
| let der_bytes = cert_ctx.to_der(); | ||
| let cert = | ||
| rustls::pki_types::CertificateDer::from(der_bytes.to_vec()); | ||
| let is_ca = cert::is_ca_certificate(cert.as_ref()); | ||
| if store.add(cert).is_ok() { | ||
| *self.x509_cert_count.write() += 1; | ||
| if is_ca { | ||
| *self.ca_cert_count.write() += 1; | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // If there were errors but some certs loaded, just continue | ||
| // If NO certs loaded and there were errors, report the first error | ||
| if *self.x509_cert_count.read() == 0 && !result.errors.is_empty() { | ||
| return Err(vm.new_os_error(format!( | ||
| "Failed to load native certificates: {}", | ||
| result.errors[0] | ||
| ))); | ||
| if *self.x509_cert_count.read() == 0 { | ||
| return Err(vm.new_os_error( | ||
| "Failed to load certificates from Windows store".to_owned(), | ||
| )); | ||
| } | ||
|
|
||
| Ok(()) | ||
| } | ||
|
|
||
| Ok(()) | ||
| #[cfg(not(windows))] | ||
| { | ||
| let result = rustls_native_certs::load_native_certs(); | ||
|
|
||
| // Load successfully found certificates | ||
| for cert in result.certs { | ||
| let is_ca = cert::is_ca_certificate(cert.as_ref()); | ||
| if store.add(cert).is_ok() { | ||
| *self.x509_cert_count.write() += 1; | ||
| if is_ca { | ||
| *self.ca_cert_count.write() += 1; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // If there were errors but some certs loaded, just continue | ||
| // If NO certs loaded and there were errors, report the first error | ||
| if *self.x509_cert_count.read() == 0 && !result.errors.is_empty() { | ||
| return Err(vm.new_os_error(format!( | ||
| "Failed to load native certificates: {}", | ||
| result.errors[0] | ||
| ))); | ||
| } | ||
|
|
||
| Ok(()) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider improving error handling to match non-Windows behavior.
The Windows implementation returns an error if x509_cert_count is 0, regardless of whether store opening succeeded. The non-Windows code (lines 1512-1517) only returns an error if both x509_cert_count == 0 AND there were errors during loading.
The non-Windows approach is more resilient—it succeeds if any certificates loaded, even if some failed. Consider updating the Windows implementation to allow partial success and provide more specific error messages (e.g., "stores exist but are empty" vs "failed to open stores").
+ let had_errors = store_names.iter().all(|&name| {
+ open_fns.iter().all(|open_fn| open_fn(name).is_err())
+ });
+
if *self.x509_cert_count.read() == 0 {
+ if !had_errors {
+ // Stores opened but were empty - not an error
+ return Ok(());
+ }
return Err(vm.new_os_error(
"Failed to load certificates from Windows store".to_owned(),
));
}Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In crates/stdlib/src/ssl.rs around lines 1459-1520, the Windows branch currently
returns an error whenever x509_cert_count == 0; change it to mirror the
non-Windows behavior by collecting errors while attempting to open stores and
iterate certs, and only return an error if no certs were loaded and there were
errors. Specifically: add a Vec<String> to accumulate failures from
open_fn(store_name) and any store iteration failures, continue loading any certs
that succeed and increment counts as before, and after the loops if
x509_cert_count == 0 then if errors is non-empty return a vm.os_error with the
first error, else return a vm.os_error stating the stores were opened but
contain no certificates; otherwise return Ok(()).
Summary by CodeRabbit
New Features
Improvements
✏️ Tip: You can customize this high-level summary in your review settings.